mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dd1c17ff4 | ||
|
|
fa5479ee5f | ||
|
|
03412cacba | ||
|
|
01a38a0b11 | ||
|
|
f43c14bd78 | ||
|
|
fb23452383 | ||
|
|
ab7dde1450 | ||
|
|
8d9bc2f5ff | ||
|
|
7651ccc84e | ||
|
|
1a6b95b388 | ||
|
|
78ec1e7512 | ||
|
|
7e38d26c33 | ||
|
|
ed09dd4e9e | ||
|
|
5731b79554 | ||
|
|
eaa22a9d13 | ||
|
|
b2bdfe8482 | ||
|
|
fea531be9a | ||
|
|
7c69d38588 | ||
|
|
a088ee56b0 | ||
|
|
ae669af904 | ||
|
|
d1ddf05e38 | ||
|
|
51279a98b3 | ||
|
|
bf33a4f82d | ||
|
|
fff0d741c3 | ||
|
|
e83d0ee820 | ||
|
|
09f3eecf56 | ||
|
|
2bd4326ff6 | ||
|
|
c13168b60c | ||
|
|
ea3871d0c4 | ||
|
|
70a2b11271 | ||
|
|
3cf39e072e | ||
|
|
413b86cc4a | ||
|
|
a6107fcfdf | ||
|
|
a064ade1e0 | ||
|
|
df35aa7942 | ||
|
|
cd49c5f88d | ||
|
|
1541ad2160 | ||
|
|
c78b7b1a24 | ||
|
|
9c7a645e18 | ||
|
|
4acf38031a | ||
|
|
4cd7271e30 | ||
|
|
3f630ab1b0 | ||
|
|
04cb684fd4 | ||
|
|
4c843571ea | ||
|
|
1326498802 | ||
|
|
b7ebd8c4a6 | ||
|
|
24e0a69480 | ||
|
|
4bcb2bdede | ||
|
|
d27f3eb8a4 | ||
|
|
d3e4481112 | ||
|
|
1d1d6b3d98 | ||
|
|
90b8a22a71 | ||
|
|
8dbfb8ab76 | ||
|
|
e29ff1c848 | ||
|
|
585f6ffc9b | ||
|
|
46b94c17d6 | ||
|
|
7af8007447 | ||
|
|
16a2b2f566 | ||
|
|
ea2a90c3c5 | ||
|
|
5cda750e5e | ||
|
|
4e143d45c8 | ||
|
|
4c50980d81 | ||
|
|
2954f5f04d | ||
|
|
cac4c1eb1e | ||
|
|
0b1f30d98c | ||
|
|
c7b1e8d772 | ||
|
|
a4f7512d44 | ||
|
|
0d3ad80659 | ||
|
|
aba1a73e28 | ||
|
|
dca31b2ca3 | ||
|
|
0cb378ca31 | ||
|
|
cf551d2cc7 | ||
|
|
ac0c8a68f6 | ||
|
|
5986dcdd2f | ||
|
|
6be6eb2227 | ||
|
|
d34015eec5 | ||
|
|
255c5bfaca | ||
|
|
01c6754928 | ||
|
|
8eaf884f69 | ||
|
|
699a2bb7ab | ||
|
|
4a2dcd20d1 | ||
|
|
4e98fb75d6 | ||
|
|
64e66e732f | ||
|
|
7aec627f6b | ||
|
|
59a2fec176 | ||
|
|
edc5a5a94f | ||
|
|
c5b7edad82 | ||
|
|
124ffac4e4 | ||
|
|
6d2a36fb2b | ||
|
|
28b43b3e1d | ||
|
|
f7feaf158d | ||
|
|
2396f707c6 | ||
|
|
d4d8e1b1ba | ||
|
|
44fec2c729 | ||
|
|
a80a6913e3 | ||
|
|
0eac04c220 | ||
|
|
29dd758302 | ||
|
|
5c45adc7f0 | ||
|
|
ad22cf08cd | ||
|
|
2c2ae64194 | ||
|
|
97c2dadd16 | ||
|
|
b36e1e3baf | ||
|
|
2da35fec17 | ||
|
|
bdeac74cfc | ||
|
|
6516d7cb15 | ||
|
|
31cf76042d | ||
|
|
c4c4dcf2b3 | ||
|
|
03145630f8 | ||
|
|
e2fcac322f | ||
|
|
beaff4d650 | ||
|
|
79af96ddde | ||
|
|
e439720c9d | ||
|
|
48d0185ea4 | ||
|
|
e2592b4e0b | ||
|
|
2967866e3d | ||
|
|
b566ea5c3f | ||
|
|
8f6eaeac2c | ||
|
|
b4facaeb3c | ||
|
|
d12b7ccc6b | ||
|
|
453e8bd0a0 | ||
|
|
9204d390ae | ||
|
|
b70ce0015c | ||
|
|
d113827753 | ||
|
|
c67f877857 | ||
|
|
0ec719e429 | ||
|
|
17f7b11148 | ||
|
|
966b017670 | ||
|
|
4c98070b3c | ||
|
|
3681d6ee1c | ||
|
|
0af17cdc33 | ||
|
|
2aae1f5e30 | ||
|
|
d18f2a7bfd | ||
|
|
9046fe8d3a | ||
|
|
78c7ee247a | ||
|
|
d5adb85e5b | ||
|
|
69f953fd9b | ||
|
|
484677b4b1 | ||
|
|
b10a8e728f | ||
|
|
25f25a8767 | ||
|
|
0c053e4a2c | ||
|
|
3f6521cc9b | ||
|
|
a074491d5b | ||
|
|
a291164953 | ||
|
|
43c55b58d2 | ||
|
|
e7298f8162 | ||
|
|
ddf990296b | ||
|
|
ead8aa7800 | ||
|
|
7a9dd1ac9b | ||
|
|
1c97c22eff | ||
|
|
bbf621a8c4 | ||
|
|
8efa89165c | ||
|
|
4f8aaf9244 | ||
|
|
a97edef380 | ||
|
|
eefae24aa3 | ||
|
|
54bffc91ae | ||
|
|
63f5ef9e14 | ||
|
|
034f27a8dd | ||
|
|
c2f6311367 | ||
|
|
6f00a48772 | ||
|
|
b3dba67405 | ||
|
|
c9a4235669 | ||
|
|
ae0d52274c | ||
|
|
8973763866 | ||
|
|
3d799ae7fe | ||
|
|
8b10115390 | ||
|
|
d2e010c439 | ||
|
|
15867ab423 | ||
|
|
22c9e99fa3 | ||
|
|
ee262f6aad | ||
|
|
af64af2397 | ||
|
|
1feead2260 | ||
|
|
d3dcd24b4d | ||
|
|
fd1e6796ef | ||
|
|
3ea0f0cbaa | ||
|
|
f3e3311598 | ||
|
|
0dc50a93a4 | ||
|
|
fda8e61be4 | ||
|
|
ac1d4b4a7a | ||
|
|
c719e274d5 | ||
|
|
e4990f8ec5 | ||
|
|
62afd3342e | ||
|
|
6e8a89e6f1 | ||
|
|
aa2437cfb8 | ||
|
|
4a710ecdfc | ||
|
|
3ef5bdfeda | ||
|
|
7915dda35f | ||
|
|
9120e16683 | ||
|
|
a1ebc7090d | ||
|
|
054b4636e0 | ||
|
|
e3e7b060b7 | ||
|
|
5ac9c75521 | ||
|
|
07710e0995 | ||
|
|
d6a67f5f2b | ||
|
|
2675623aea | ||
|
|
94263c43d0 | ||
|
|
d8ec03874f | ||
|
|
a7247f5b8b | ||
|
|
4d37581694 | ||
|
|
5d7ddebcad | ||
|
|
53df0eb707 | ||
|
|
8babad9c7c | ||
|
|
8db7aa07bd | ||
|
|
42f4b06ac8 | ||
|
|
f4b50368ba | ||
|
|
db80417bd7 | ||
|
|
7a6f2ecc8c | ||
|
|
f5d556a7f9 | ||
|
|
2aae46d632 | ||
|
|
19ebc6d6b3 | ||
|
|
f88c29e083 | ||
|
|
6ed9899dc7 | ||
|
|
9de7698a5c | ||
|
|
112d2d6058 | ||
|
|
ddb8346711 | ||
|
|
8dd3faf395 | ||
|
|
35f3e8708b | ||
|
|
cfe3fcc9e7 | ||
|
|
66a6659a6e | ||
|
|
88ae3daa42 | ||
|
|
08b8fe01ab | ||
|
|
731132d4b3 | ||
|
|
98acff802f | ||
|
|
5f11f485a2 | ||
|
|
34f3169dda | ||
|
|
a3ef8f814b | ||
|
|
385dd6fc23 | ||
|
|
9af4168ae2 | ||
|
|
a5e0e31b74 | ||
|
|
b385dc8c26 | ||
|
|
92c012b55a | ||
|
|
641f6c05d8 | ||
|
|
788f6b44a6 | ||
|
|
63a4525f06 | ||
|
|
3e34a3ef72 | ||
|
|
0c5e8ca199 | ||
|
|
ff23fb0086 | ||
|
|
56f41d5e34 | ||
|
|
4700a239b9 | ||
|
|
bd5abfb969 | ||
|
|
b93fa75377 | ||
|
|
681ce9c60c | ||
|
|
dd0f0fe415 | ||
|
|
119040fc50 | ||
|
|
551e5688da | ||
|
|
56c1035581 | ||
|
|
ba1e907c79 | ||
|
|
2a3a27c56d | ||
|
|
647af34f5b | ||
|
|
993be6394e | ||
|
|
9a27505315 | ||
|
|
2e37f5dee3 | ||
|
|
03e486c082 | ||
|
|
edc83305a4 | ||
|
|
66e7c51064 | ||
|
|
60244aaf16 | ||
|
|
443391c700 | ||
|
|
47dbafacd4 | ||
|
|
5b6811d073 | ||
|
|
7516116bb7 | ||
|
|
e6014ea4dd | ||
|
|
362abfe284 | ||
|
|
ad4880997a | ||
|
|
592becc126 | ||
|
|
c38765301e | ||
|
|
5f27426f59 | ||
|
|
d924f8bff8 | ||
|
|
d14c61b160 |
No files matched your search
@@ -18,30 +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
|
||||
|
||||
- uses: MCJack123/ghaction-generate-release-hashes@v4
|
||||
if: "github.event_name == 'release'"
|
||||
with:
|
||||
hash-type: sha256
|
||||
file-name: hashes.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: "github.event_name == 'release'"
|
||||
with:
|
||||
name: Artifact Hashes
|
||||
path: hashes.txt
|
||||
@@ -1,5 +1,6 @@
|
||||
.idea/workspace.xml
|
||||
.idea/discord.xml
|
||||
.idea/developer-tools.xml
|
||||
build/
|
||||
dist/
|
||||
output/
|
||||
|
||||
Generated
+2
-2
@@ -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>
|
||||
Generated
+10
-10
@@ -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.20" />
|
||||
<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.20/kotlin-stdlib-jdk8-2.0.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20.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.20/kotlin-stdlib-jdk7-2.0.20.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.20/kotlin-stdlib-jdk8-2.0.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-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.20/kotlin-stdlib-jdk7-2.0.20-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.20/kotlin-stdlib-jdk8-2.0.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-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.20/kotlin-stdlib-jdk7-2.0.20-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.1">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.2">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
|
||||
+10
-13
@@ -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.1" />
|
||||
<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.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.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.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.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 />
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.framework.datatest" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.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-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.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-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.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/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/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+18
-21
@@ -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.1" />
|
||||
<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.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.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.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.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.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.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.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-jvm-5.9.1.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 />
|
||||
|
||||
+12
-4
@@ -1,12 +1,20 @@
|
||||
<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>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.0/kotlin-result-jvm-2.0.0-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/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.0/kotlin-result-jvm-2.0.0-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/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+1
-1
@@ -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>
|
||||
Generated
+2
-1
@@ -12,9 +12,10 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
@@ -29,6 +29,8 @@ How to get it/build it
|
||||
- Or, if you want/need a bleeding edge development version, you can:
|
||||
- download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions).
|
||||
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
|
||||
Note that if you are not using *gradle* to build it, you might have to perform some manual
|
||||
tasks once to make it compile fully. These are explained in the linked instructions.
|
||||
- Alternatively, you can also install the compiler as a package on some linux distros:
|
||||
- Arch (via AUR): [`prog8`](https://aur.archlinux.org/packages/prog8)
|
||||
|
||||
@@ -52,26 +54,23 @@ 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.
|
||||
- modularity, symbol scoping, subroutines
|
||||
- programs run very fast because it's compiled to native machine code
|
||||
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
|
||||
- modularity, symbol scoping, subroutines. No need for forward declarations.
|
||||
- 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)
|
||||
- floating point math is supported on certain targets
|
||||
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
|
||||
- automatic static variable allocations, automatic string and array variables and string sharing
|
||||
- subroutines with input parameters and result values
|
||||
- high-level program optimizations
|
||||
- no need for forward declarations
|
||||
- small program boilerplate/compilersupport overhead
|
||||
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- conditional branches
|
||||
- conditional branches that map 1:1 to cpu status flags
|
||||
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
|
||||
- ``in`` expression for concise and efficient multi-value/containment check
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
|
||||
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
|
||||
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- 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)
|
||||
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
|
||||
- encode strings and characters into petscii or screencodes or even other encodings
|
||||
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
@@ -119,14 +118,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 +160,6 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
when compiled an ran on a C-64 you'll get:
|
||||
|
||||

|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.PHONY: all clean emu
|
||||
|
||||
all: benchmark.prg
|
||||
|
||||
clean:
|
||||
rm -f *.prg *.PRG *.asm *.vice-* *.BIN *.PAL *.zip *.7z
|
||||
|
||||
emu: benchmark.prg
|
||||
x16emu -run -prg $< -warp
|
||||
|
||||
benchmark.prg: benchmark.p8 b_3d.p8 b_adpcm.p8 b_circles.p8 b_life.p8 b_mandelbrot.p8 b_maze.p8 b_queens.p8 b_textelite.p8
|
||||
p8compile $< -target cx16
|
||||
@@ -0,0 +1,109 @@
|
||||
%import textio
|
||||
%import math
|
||||
|
||||
rotate3d {
|
||||
const ubyte WIDTH = 40
|
||||
const ubyte HEIGHT = 30
|
||||
|
||||
sub benchmark(uword max_time) -> uword {
|
||||
|
||||
uword anglex
|
||||
uword angley
|
||||
uword anglez
|
||||
uword frames
|
||||
|
||||
txt.nl()
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
matrix_math.rotate_vertices(msb(anglex), msb(angley), msb(anglez))
|
||||
draw_edges() ; doesn't really draw anything in the benchmark, but does do the screen calculations
|
||||
anglex+=500
|
||||
angley+=215
|
||||
anglez+=453
|
||||
frames++
|
||||
}
|
||||
|
||||
return frames
|
||||
}
|
||||
|
||||
sub draw_edges() {
|
||||
|
||||
; plot the points of the 3d cube
|
||||
; first the points on the back, then the points on the front (painter algorithm)
|
||||
|
||||
ubyte @zp i
|
||||
word @zp rz
|
||||
word @zp persp
|
||||
byte @shared sx
|
||||
byte @shared sy
|
||||
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz >= 10 {
|
||||
persp = 600 + rz/64
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
|
||||
;; txt.setcc(sx as ubyte, sy as ubyte, 46, 7)
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz < 10 {
|
||||
persp = 600 + rz/64
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
|
||||
;; txt.setcc(sx as ubyte, sy as ubyte, 81, 7)
|
||||
}
|
||||
}
|
||||
|
||||
txt.chrout('.')
|
||||
}
|
||||
}
|
||||
|
||||
matrix_math {
|
||||
; vertices
|
||||
word[] @split xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
|
||||
word[] @split ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
|
||||
word[] @split zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
word[len(xcoor)] @split rotatedx
|
||||
word[len(ycoor)] @split rotatedy
|
||||
word[len(zcoor)] @split rotatedz
|
||||
|
||||
sub rotate_vertices(ubyte ax, ubyte ay, ubyte az) {
|
||||
; rotate around origin (0,0,0)
|
||||
|
||||
; set up the 3d rotation matrix values
|
||||
word wcosa = math.cos8(ax)
|
||||
word wsina = math.sin8(ax)
|
||||
word wcosb = math.cos8(ay)
|
||||
word wsinb = math.sin8(ay)
|
||||
word wcosc = math.cos8(az)
|
||||
word wsinc = math.sin8(az)
|
||||
|
||||
word wcosa_sinb = wcosa*wsinb / 128
|
||||
word wsina_sinb = wsina*wsinb / 128
|
||||
|
||||
word Axx = wcosa*wcosb / 128
|
||||
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) / 128
|
||||
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) / 128
|
||||
word Ayx = wsina*wcosb / 128
|
||||
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) / 128
|
||||
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) / 128
|
||||
word Azx = -wsinb
|
||||
word Azy = wcosb*wsinc / 128
|
||||
word Azz = wcosb*wcosc / 128
|
||||
|
||||
ubyte @zp i
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
; don't normalize by dividing by 128, instead keep some precision for perspective calc later
|
||||
rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
adpcm {
|
||||
|
||||
sub decode_benchmark(uword max_time) -> uword {
|
||||
uword num_blocks
|
||||
txt.nl()
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
adpcm.init(0,0)
|
||||
uword @requirezp nibbles_ptr = $a000 ; for benchmark purposes, the exact nibbles don't really matter, so we just take the basic ROM as input
|
||||
repeat 252/2 {
|
||||
unroll 2 {
|
||||
ubyte @zp nibble = @(nibbles_ptr)
|
||||
adpcm.decode_nibble(nibble & 15) ; first word (note: upper nibble needs to be zero!)
|
||||
adpcm.decode_nibble(nibble>>4) ; second word (note: upper nibble is zero, after the shifts.)
|
||||
nibbles_ptr++
|
||||
}
|
||||
}
|
||||
num_blocks++
|
||||
txt.chrout('.')
|
||||
}
|
||||
|
||||
return num_blocks
|
||||
}
|
||||
|
||||
; IMA ADPCM decoder. Supports mono and stereo streams.
|
||||
|
||||
ubyte[] t_index = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8]
|
||||
uword[] @split t_step = [
|
||||
7, 8, 9, 10, 11, 12, 13, 14,
|
||||
16, 17, 19, 21, 23, 25, 28, 31,
|
||||
34, 37, 41, 45, 50, 55, 60, 66,
|
||||
73, 80, 88, 97, 107, 118, 130, 143,
|
||||
157, 173, 190, 209, 230, 253, 279, 307,
|
||||
337, 371, 408, 449, 494, 544, 598, 658,
|
||||
724, 796, 876, 963, 1060, 1166, 1282, 1411,
|
||||
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
|
||||
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
|
||||
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
|
||||
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
|
||||
32767]
|
||||
|
||||
uword @requirezp predict ; decoded 16 bit pcm sample for first channel.
|
||||
ubyte @requirezp index
|
||||
uword @requirezp pstep
|
||||
|
||||
sub init(uword startPredict, ubyte startIndex) {
|
||||
; initialize first decoding channel.
|
||||
predict = startPredict
|
||||
index = startIndex
|
||||
pstep = t_step[index]
|
||||
}
|
||||
|
||||
sub decode_nibble(ubyte @zp nibble) {
|
||||
; Decoder for a single nibble for the first channel. (value of 'nibble' needs to be strictly 0-15 !)
|
||||
; This is the hotspot of the decoder algorithm!
|
||||
; Note that the generated assembly from this is pretty efficient,
|
||||
; rewriting it by hand in asm seems to improve it only ~10%.
|
||||
cx16.r0s = 0 ; difference
|
||||
if nibble & %0100 !=0
|
||||
cx16.r0s += pstep
|
||||
pstep >>= 1
|
||||
if nibble & %0010 !=0
|
||||
cx16.r0s += pstep
|
||||
pstep >>= 1
|
||||
if nibble & %0001 !=0
|
||||
cx16.r0s += pstep
|
||||
pstep >>= 1
|
||||
cx16.r0s += pstep
|
||||
if nibble & %1000 !=0
|
||||
predict -= cx16.r0
|
||||
else
|
||||
predict += cx16.r0
|
||||
|
||||
; NOTE: the original C/Python code uses a 32 bits prediction value and clips it to a 16 bit word
|
||||
; but for speed reasons we only work with 16 bit words here all the time (with possible clipping error)
|
||||
; if predicted > 32767:
|
||||
; predicted = 32767
|
||||
; elif predicted < -32767:
|
||||
; predicted = - 32767
|
||||
|
||||
index += t_index[nibble]
|
||||
if_neg
|
||||
index = 0
|
||||
else if index >= len(t_step)-1
|
||||
index = len(t_step)-1
|
||||
pstep = t_step[index]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
%import gfx2
|
||||
%import math
|
||||
|
||||
circles {
|
||||
const ubyte MAX_NUM_CIRCLES = 80
|
||||
const ubyte GROWTH_RATE = 4
|
||||
uword[MAX_NUM_CIRCLES] @split circle_x
|
||||
uword[MAX_NUM_CIRCLES] @split circle_y
|
||||
ubyte[MAX_NUM_CIRCLES] circle_radius
|
||||
ubyte color
|
||||
uword total_num_circles
|
||||
|
||||
sub draw(bool use_kernal, uword max_time) -> uword {
|
||||
if use_kernal
|
||||
void cx16.set_screen_mode(128)
|
||||
else
|
||||
gfx2.screen_mode(1)
|
||||
|
||||
math.rndseed(12345,6789)
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
total_num_circles = 0
|
||||
color = 16
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
if use_kernal {
|
||||
cx16.GRAPH_set_colors(0,0,0)
|
||||
cx16.GRAPH_clear()
|
||||
}
|
||||
else
|
||||
gfx2.clear_screen(0)
|
||||
total_num_circles += draw_circles(use_kernal, max_time)
|
||||
}
|
||||
|
||||
if use_kernal
|
||||
void cx16.set_screen_mode(3)
|
||||
else {
|
||||
gfx2.screen_mode(0)
|
||||
}
|
||||
|
||||
return total_num_circles
|
||||
}
|
||||
|
||||
sub draw_circles(bool use_kernal, uword max_time) -> uword {
|
||||
uword @zp x
|
||||
uword @zp y
|
||||
ubyte @zp radius
|
||||
|
||||
ubyte num_circles
|
||||
|
||||
while num_circles<MAX_NUM_CIRCLES and cbm.RDTIM16()<max_time {
|
||||
x = math.rndw() % 320
|
||||
y = math.rndw() % 240
|
||||
radius = GROWTH_RATE
|
||||
if not_colliding() {
|
||||
while not_edge() and not_colliding() {
|
||||
radius += GROWTH_RATE
|
||||
}
|
||||
radius -= GROWTH_RATE
|
||||
if radius>0 {
|
||||
color++
|
||||
if color==0
|
||||
color=16
|
||||
if use_kernal {
|
||||
cx16.GRAPH_set_colors(color, 255-color, 0)
|
||||
cx16.GRAPH_draw_oval(x-radius, y-radius, radius*2, radius*2, true)
|
||||
}
|
||||
else
|
||||
gfx2.disc(x, y as ubyte, radius, color)
|
||||
circle_x[num_circles] = x
|
||||
circle_y[num_circles] = y
|
||||
circle_radius[num_circles] = radius
|
||||
num_circles++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return num_circles
|
||||
|
||||
sub not_colliding() -> bool {
|
||||
if num_circles==0
|
||||
return true
|
||||
ubyte @zp c
|
||||
for c in 0 to num_circles-1 {
|
||||
if distance(c) < (radius as uword) + circle_radius[c]
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub distance(ubyte cix) -> uword {
|
||||
word dx = x as word - circle_x[cix]
|
||||
word dy = y as word - circle_y[cix]
|
||||
uword sqx = dx*dx as uword
|
||||
uword sqy = dy*dy as uword
|
||||
return sqrt(sqx + sqy)
|
||||
}
|
||||
|
||||
sub not_edge() -> bool {
|
||||
if x as word - radius < 0
|
||||
return false
|
||||
if x + radius >= 320
|
||||
return false
|
||||
if y as word - radius < 0
|
||||
return false
|
||||
if y + radius >= 240
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
; conway's game of life.
|
||||
|
||||
%import math
|
||||
%import textio
|
||||
|
||||
life {
|
||||
const ubyte WIDTH = 40
|
||||
const ubyte HEIGHT = 30
|
||||
const uword STRIDE = $0002+WIDTH
|
||||
uword world1 = memory("world1", (WIDTH+2)*(HEIGHT+2), 0)
|
||||
uword world2 = memory("world2", (WIDTH+2)*(HEIGHT+2), 0)
|
||||
uword @requirezp active_world = world1
|
||||
|
||||
sub benchmark(uword max_time) -> uword {
|
||||
txt.clear_screen()
|
||||
sys.memset(world1, (WIDTH+2)*(HEIGHT+2), 0)
|
||||
sys.memset(world2, (WIDTH+2)*(HEIGHT+2), 0)
|
||||
|
||||
set_start_gen()
|
||||
|
||||
uword gen
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
next_gen()
|
||||
gen++
|
||||
}
|
||||
|
||||
return gen
|
||||
}
|
||||
|
||||
sub set_start_gen() {
|
||||
|
||||
; some way to set a custom start generation:
|
||||
; str start_gen = " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " ** " +
|
||||
; " * * " +
|
||||
; " * " +
|
||||
; " * * " +
|
||||
; " ****** " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " "
|
||||
;
|
||||
; for y in 0 to 15 {
|
||||
; for x in 0 to 15 {
|
||||
; if start_gen[y*16 + x]=='*'
|
||||
; active_world[offset + x] = 1
|
||||
; }
|
||||
; offset += STRIDE
|
||||
; }
|
||||
|
||||
; randomize whole world
|
||||
math.rndseed(12345,9999)
|
||||
uword offset = STRIDE+1
|
||||
ubyte x
|
||||
ubyte y
|
||||
for y in 0 to HEIGHT-1 {
|
||||
for x in 0 to WIDTH-1 {
|
||||
active_world[offset+x] = math.rnd() & 1
|
||||
}
|
||||
offset += STRIDE
|
||||
}
|
||||
}
|
||||
|
||||
sub next_gen() {
|
||||
const ubyte DXOFFSET = 0
|
||||
const ubyte DYOFFSET = 0
|
||||
ubyte[2] cell_chars = [sc:' ', sc:'●']
|
||||
|
||||
uword @requirezp new_world = world1
|
||||
if active_world == world1
|
||||
new_world = world2
|
||||
|
||||
; To avoid re-calculating word index lookups into the new- and active world arrays,
|
||||
; we calculate the required pointer values upfront.
|
||||
; Inside the loop we can use ptr+x just fine (results in efficient LDA (ptr),Y instruction because x is a byte type),
|
||||
; and for each row we simply add the stride to the pointer.
|
||||
; It's more readable to use active_world[offset] etc, but offset is a word value, and this produces
|
||||
; inefficient assembly code because we can't use a register indexed mode in this case. Costly inside a loop.
|
||||
|
||||
uword @requirezp new_world_ptr = new_world + STRIDE+1-DXOFFSET
|
||||
uword @requirezp active_world_ptr = active_world + STRIDE+1-DXOFFSET
|
||||
|
||||
ubyte x
|
||||
ubyte y
|
||||
for y in DYOFFSET to HEIGHT+DYOFFSET-1 {
|
||||
|
||||
cx16.vaddr_autoincr(1, $b000 + 256*y, 0, 2) ; allows us to use simple Vera data byte assigns later instead of setchr() calls
|
||||
|
||||
for x in DXOFFSET to WIDTH+DXOFFSET-1 {
|
||||
; count the living neighbors
|
||||
ubyte cell = @(active_world_ptr + x)
|
||||
uword @requirezp ptr = active_world_ptr + x - STRIDE - 1
|
||||
ubyte neighbors = @(ptr) + @(ptr+1) + @(ptr+2) +
|
||||
@(ptr+STRIDE) + cell + @(ptr+STRIDE+2) +
|
||||
@(ptr+STRIDE*2) + @(ptr+STRIDE*2+1) + @(ptr+STRIDE*2+2)
|
||||
|
||||
; apply game of life rules
|
||||
if neighbors==3
|
||||
cell=1
|
||||
else if neighbors!=4
|
||||
cell=0
|
||||
@(new_world_ptr + x) = cell
|
||||
|
||||
; draw new cell
|
||||
; txt.setchr(x,y,cell_chars[cell])
|
||||
cx16.VERA_DATA0 = cell_chars[cell]
|
||||
}
|
||||
active_world_ptr += STRIDE
|
||||
new_world_ptr += STRIDE
|
||||
}
|
||||
|
||||
active_world = new_world
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
%import textio
|
||||
%import floats
|
||||
|
||||
mandelbrot {
|
||||
const ubyte width = 39
|
||||
const ubyte height = 29
|
||||
const ubyte max_iter = 15
|
||||
|
||||
sub calc(uword max_time) -> uword {
|
||||
uword num_pixels
|
||||
ubyte pixelx
|
||||
ubyte pixely
|
||||
|
||||
txt.home()
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
while cbm.RDTIM16() < max_time {
|
||||
for pixely in 0 to height-1 {
|
||||
float yy = (pixely as float)/0.40/height - 1.3
|
||||
|
||||
for pixelx in 0 to width-1 {
|
||||
float xx = (pixelx as float)/0.32/width - 2.2
|
||||
|
||||
float xsquared = 0.0
|
||||
float ysquared = 0.0
|
||||
float x = 0.0
|
||||
float y = 0.0
|
||||
ubyte iter = 0
|
||||
|
||||
while iter<max_iter and xsquared+ysquared<4.0 {
|
||||
y = x*y*2.0 + yy
|
||||
x = xsquared - ysquared + xx
|
||||
xsquared = x*x
|
||||
ysquared = y*y
|
||||
iter++
|
||||
}
|
||||
txt.color2(1, max_iter-iter)
|
||||
txt.spc()
|
||||
num_pixels++
|
||||
|
||||
if cbm.RDTIM16()>=max_time
|
||||
goto finished
|
||||
}
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
txt.clear_screen()
|
||||
}
|
||||
|
||||
finished:
|
||||
txt.color2(1, 6)
|
||||
return num_pixels
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
%import textio
|
||||
%import math
|
||||
|
||||
; Even though prog8 only has support for extremely limited recursion,
|
||||
; you can write recursive algorithms with a bit of extra work by building your own explicit stack structure.
|
||||
; This program shows a depth-first maze generation algorithm (1 possible path from start to finish),
|
||||
; and a depth-first maze solver algorithm, both using a stack to store the path taken.
|
||||
|
||||
; Note: this program can be compiled for multiple target systems.
|
||||
|
||||
maze {
|
||||
uword score
|
||||
|
||||
sub bench(uword max_time) -> uword {
|
||||
txt.nl()
|
||||
score=0
|
||||
math.rndseed(2345,44332)
|
||||
cbm.SETTIM(0,0,0)
|
||||
while cbm.RDTIM16()<max_time {
|
||||
maze.initialize()
|
||||
maze.drawStartFinish()
|
||||
if maze.generate(max_time) {
|
||||
maze.openpassages()
|
||||
maze.drawStartFinish()
|
||||
if maze.solve(max_time) {
|
||||
maze.drawStartFinish()
|
||||
} else break
|
||||
} else break
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
const uword screenwidth = 40
|
||||
const uword screenheight = 30
|
||||
|
||||
const ubyte numCellsHoriz = (screenwidth-1) / 2
|
||||
const ubyte numCellsVert = (screenheight-1) / 2
|
||||
|
||||
; maze start and finish cells
|
||||
const ubyte startCx = 0
|
||||
const ubyte startCy = 0
|
||||
const ubyte finishCx = numCellsHoriz-1
|
||||
const ubyte finishCy = numCellsVert-1
|
||||
|
||||
; cell properties
|
||||
const ubyte STONE = 128
|
||||
const ubyte WALKED = 64
|
||||
const ubyte BACKTRACKED = 32
|
||||
const ubyte UP = 1
|
||||
const ubyte RIGHT = 2
|
||||
const ubyte DOWN = 4
|
||||
const ubyte LEFT = 8
|
||||
const ubyte WALLCOLOR = 12
|
||||
const ubyte EMPTYCOLOR = 0
|
||||
|
||||
; unfortunately on larger screens (cx16), the number of cells exceeds 256 and doesn't fit in a regular array anymore.
|
||||
uword cells = memory("cells", numCellsHoriz*numCellsVert, 0)
|
||||
|
||||
ubyte[256] cx_stack
|
||||
ubyte[256] cy_stack
|
||||
ubyte stackptr
|
||||
|
||||
ubyte[4] directionflags = [LEFT,RIGHT,UP,DOWN]
|
||||
|
||||
sub generate(uword max_time) -> bool {
|
||||
ubyte cx = startCx
|
||||
ubyte cy = startCy
|
||||
|
||||
stackptr = 0
|
||||
@(celladdr(cx,cy)) &= ~STONE
|
||||
drawCell(cx, cy)
|
||||
uword cells_to_carve = numCellsHoriz * numCellsVert - 1
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
carve_restart_after_repath:
|
||||
ubyte direction = choose_uncarved_direction()
|
||||
if direction==0 {
|
||||
;backtrack
|
||||
stackptr--
|
||||
if stackptr==255 {
|
||||
; stack empty.
|
||||
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
|
||||
if cells_to_carve!=0 {
|
||||
if repath()
|
||||
goto carve_restart_after_repath
|
||||
}
|
||||
return true
|
||||
}
|
||||
cx = cx_stack[stackptr]
|
||||
cy = cy_stack[stackptr]
|
||||
} else {
|
||||
cx_stack[stackptr] = cx
|
||||
cy_stack[stackptr] = cy
|
||||
stackptr++
|
||||
if stackptr==0 {
|
||||
; stack overflow, we can't track our path any longer.
|
||||
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
|
||||
if cells_to_carve!=0 {
|
||||
if repath()
|
||||
goto carve_restart_after_repath
|
||||
}
|
||||
return true
|
||||
}
|
||||
@(celladdr(cx,cy)) |= direction
|
||||
when direction {
|
||||
UP -> {
|
||||
cy--
|
||||
@(celladdr(cx,cy)) |= DOWN
|
||||
}
|
||||
RIGHT -> {
|
||||
cx++
|
||||
@(celladdr(cx,cy)) |= LEFT
|
||||
|
||||
score++
|
||||
}
|
||||
DOWN -> {
|
||||
cy++
|
||||
@(celladdr(cx,cy)) |= UP
|
||||
}
|
||||
LEFT -> {
|
||||
cx--
|
||||
@(celladdr(cx,cy)) |= RIGHT
|
||||
}
|
||||
}
|
||||
@(celladdr(cx,cy)) &= ~STONE
|
||||
cells_to_carve--
|
||||
drawCell(cx, cy)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
sub repath() -> bool {
|
||||
; repath: try to find a new start cell with possible directions.
|
||||
; we limit our number of searches so that the algorith doesn't get stuck
|
||||
; for too long on bad rng... just accept a few unused cells in that case.
|
||||
repeat 255 {
|
||||
do {
|
||||
cx = math.rnd() % numCellsHoriz
|
||||
cy = math.rnd() % numCellsVert
|
||||
} until @(celladdr(cx, cy)) & STONE ==0
|
||||
if available_uncarved()!=0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub available_uncarved() -> ubyte {
|
||||
ubyte candidates = 0
|
||||
if cx>0 and @(celladdr(cx-1, cy)) & STONE !=0
|
||||
candidates |= LEFT
|
||||
if cx<numCellsHoriz-1 and @(celladdr(cx+1, cy)) & STONE !=0
|
||||
candidates |= RIGHT
|
||||
if cy>0 and @(celladdr(cx, cy-1)) & STONE !=0
|
||||
candidates |= UP
|
||||
if cy<numCellsVert-1 and @(celladdr(cx, cy+1)) & STONE !=0
|
||||
candidates |= DOWN
|
||||
return candidates
|
||||
}
|
||||
|
||||
sub choose_uncarved_direction() -> ubyte {
|
||||
ubyte candidates = available_uncarved()
|
||||
if candidates==0
|
||||
return 0
|
||||
|
||||
repeat {
|
||||
ubyte choice = candidates & directionflags[math.rnd() & 3]
|
||||
if choice!=0
|
||||
return choice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub openpassages() {
|
||||
; open just a few extra passages, so that multiple routes are possible in theory.
|
||||
ubyte numpassages
|
||||
ubyte cx
|
||||
ubyte cy
|
||||
do {
|
||||
do {
|
||||
cx = math.rnd() % (numCellsHoriz-2) + 1
|
||||
cy = math.rnd() % (numCellsVert-2) + 1
|
||||
} until @(celladdr(cx, cy)) & STONE ==0
|
||||
ubyte direction = directionflags[math.rnd() & 3]
|
||||
if @(celladdr(cx, cy)) & direction == 0 {
|
||||
when direction {
|
||||
LEFT -> {
|
||||
if @(celladdr(cx-1,cy)) & STONE == 0 {
|
||||
@(celladdr(cx,cy)) |= LEFT
|
||||
drawCell(cx,cy)
|
||||
numpassages++
|
||||
}
|
||||
}
|
||||
RIGHT -> {
|
||||
if @(celladdr(cx+1,cy)) & STONE == 0 {
|
||||
@(celladdr(cx,cy)) |= RIGHT
|
||||
drawCell(cx,cy)
|
||||
numpassages++
|
||||
}
|
||||
}
|
||||
UP -> {
|
||||
if @(celladdr(cx,cy-1)) & STONE == 0 {
|
||||
@(celladdr(cx,cy)) |= UP
|
||||
drawCell(cx,cy)
|
||||
numpassages++
|
||||
}
|
||||
}
|
||||
DOWN -> {
|
||||
if @(celladdr(cx,cy+1)) & STONE == 0 {
|
||||
@(celladdr(cx,cy)) |= DOWN
|
||||
drawCell(cx,cy)
|
||||
numpassages++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} until numpassages==10
|
||||
}
|
||||
|
||||
sub solve(uword max_time) -> bool {
|
||||
ubyte cx = startCx
|
||||
ubyte cy = startCy
|
||||
const uword max_path_length = 1024
|
||||
|
||||
; the path through the maze can be longer than 256 so doesn't fit in a regular array.... :(
|
||||
uword pathstack = memory("pathstack", max_path_length, 0)
|
||||
uword pathstackptr = 0
|
||||
|
||||
@(celladdr(cx,cy)) |= WALKED
|
||||
; txt.setcc(cx*2+1, cy*2+1, 81, 1)
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
solve_loop:
|
||||
if cx==finishCx and cy==finishCy {
|
||||
;txt.home()
|
||||
txt.print("found! path length: ")
|
||||
txt.print_uw(pathstackptr)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
ubyte cell = @(celladdr(cx,cy))
|
||||
if cell & UP!=0 and @(celladdr(cx,cy-1)) & (WALKED|BACKTRACKED) ==0 {
|
||||
@(pathstack + pathstackptr) = UP
|
||||
;txt.setcc(cx*2+1, cy*2, 81, 3)
|
||||
cy--
|
||||
}
|
||||
else if cell & DOWN !=0 and @(celladdr(cx,cy+1)) & (WALKED|BACKTRACKED) ==0 {
|
||||
@(pathstack + pathstackptr) = DOWN
|
||||
;txt.setcc(cx*2+1, cy*2+2, 81, 3)
|
||||
cy++
|
||||
}
|
||||
else if cell & LEFT !=0 and @(celladdr(cx-1,cy)) & (WALKED|BACKTRACKED) ==0 {
|
||||
@(pathstack + pathstackptr) = LEFT
|
||||
;txt.setcc(cx*2, cy*2+1, 81, 3)
|
||||
cx--
|
||||
}
|
||||
else if cell & RIGHT !=0 and @(celladdr(cx+1,cy)) & (WALKED|BACKTRACKED) ==0 {
|
||||
@(pathstack + pathstackptr) = RIGHT
|
||||
;txt.setcc(cx*2+2, cy*2+1, 81, 3)
|
||||
cx++
|
||||
}
|
||||
else {
|
||||
; dead end, pop stack
|
||||
pathstackptr--
|
||||
if pathstackptr==65535 {
|
||||
txt.print("no solution?!\n")
|
||||
return true
|
||||
}
|
||||
@(celladdr(cx,cy)) |= BACKTRACKED
|
||||
;txt.setcc(cx*2+1, cy*2+1, 81, 2)
|
||||
when @(pathstack + pathstackptr) {
|
||||
UP -> {
|
||||
;txt.setcc(cx*2+1, cy*2+2, 81, 9)
|
||||
cy++
|
||||
}
|
||||
DOWN -> {
|
||||
;txt.setcc(cx*2+1, cy*2, 81, 9)
|
||||
cy--
|
||||
}
|
||||
LEFT -> {
|
||||
;txt.setcc(cx*2+2, cy*2+1, 81, 9)
|
||||
cx++
|
||||
}
|
||||
RIGHT -> {
|
||||
;txt.setcc(cx*2, cy*2+1, 81, 9)
|
||||
cx--
|
||||
|
||||
score++
|
||||
}
|
||||
}
|
||||
goto solve_loop
|
||||
}
|
||||
pathstackptr++
|
||||
if pathstackptr==max_path_length {
|
||||
txt.print("stack overflow, path too long\n")
|
||||
return true
|
||||
}
|
||||
@(celladdr(cx,cy)) |= WALKED
|
||||
;txt.setcc(cx*2+1, cy*2+1, 81, 1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub celladdr(ubyte cx, ubyte cy) -> uword {
|
||||
return cells+(numCellsHoriz as uword)*cy+cx
|
||||
}
|
||||
|
||||
sub drawCell(ubyte cx, ubyte cy) {
|
||||
return
|
||||
; ubyte x = cx * 2 + 1
|
||||
; ubyte y = cy * 2 + 1
|
||||
; ubyte doors = @(celladdr(cx,cy))
|
||||
; if doors & UP !=0
|
||||
; txt.setcc(x, y-1, ' ', EMPTYCOLOR)
|
||||
; if doors & RIGHT !=0
|
||||
; txt.setcc(x+1, y, ' ', EMPTYCOLOR)
|
||||
; if doors & DOWN !=0
|
||||
; txt.setcc(x, y+1, ' ', EMPTYCOLOR)
|
||||
; if doors & LEFT !=0
|
||||
; txt.setcc(x-1, y, ' ', EMPTYCOLOR)
|
||||
; if doors & STONE !=0
|
||||
; txt.setcc(x, y, 160, WALLCOLOR)
|
||||
; else
|
||||
; txt.setcc(x, y, 32, EMPTYCOLOR)
|
||||
;
|
||||
; if doors & WALKED !=0
|
||||
; txt.setcc(x, y, 81, 1)
|
||||
; if doors & BACKTRACKED !=0
|
||||
; txt.setcc(x, y, 81, 2)
|
||||
}
|
||||
|
||||
sub initialize() {
|
||||
sys.memset(cells, numCellsHoriz*numCellsVert, STONE)
|
||||
; txt.fill_screen(160, WALLCOLOR)
|
||||
drawStartFinish()
|
||||
}
|
||||
|
||||
sub drawStartFinish() {
|
||||
; txt.setcc(startCx*2+1,startCy*2+1,sc:'s',5)
|
||||
; txt.setcc(finishCx*2+1, finishCy*2+1, sc:'f', 13)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
%import textio
|
||||
|
||||
; Recursive N-Queens solver.
|
||||
; The problem is: find all possible ways to place 8 Queen chess pieces on a chess board, so that none of them attacks any other.
|
||||
; (this program prints all solutions without taking mirroring and flipping the chess board into account)
|
||||
; Note: this program can be compiled for multiple target systems.
|
||||
|
||||
queens {
|
||||
const ubyte NUMQUEENS=8
|
||||
ubyte[NUMQUEENS] board
|
||||
|
||||
sub could_place(ubyte row, ubyte col) -> bool {
|
||||
if row==0
|
||||
return true
|
||||
ubyte i
|
||||
for i in 0 to row-1 {
|
||||
if board[i]==col or board[i]-i==col-row or board[i]+i==col+row
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
uword solution_count
|
||||
uword maximum_duration
|
||||
|
||||
sub place_queen(ubyte row) -> bool {
|
||||
if row == NUMQUEENS {
|
||||
solution_count++
|
||||
txt.chrout('.')
|
||||
return cbm.RDTIM16()<maximum_duration
|
||||
}
|
||||
bool continue_running=true
|
||||
ubyte col
|
||||
for col in 0 to NUMQUEENS-1 {
|
||||
if could_place(row, col) {
|
||||
board[row] = col
|
||||
; we need to save the local variables row and col.
|
||||
sys.push(row)
|
||||
sys.push(col)
|
||||
continue_running = place_queen(row + 1)
|
||||
; restore the local variables after the recursive call.
|
||||
col = sys.pop()
|
||||
row = sys.pop()
|
||||
board[row] = 0
|
||||
|
||||
if not continue_running
|
||||
break
|
||||
}
|
||||
}
|
||||
return continue_running
|
||||
}
|
||||
|
||||
sub bench(uword max_time) -> uword {
|
||||
solution_count = 0
|
||||
maximum_duration = max_time
|
||||
txt.nl()
|
||||
cbm.SETTIM(0,0,0)
|
||||
while cbm.RDTIM16() < maximum_duration {
|
||||
void place_queen(0)
|
||||
}
|
||||
return solution_count
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,990 @@
|
||||
%import textio
|
||||
%import conv
|
||||
%import string
|
||||
%import string
|
||||
|
||||
|
||||
textelite {
|
||||
|
||||
const ubyte numforLave = 7 ; Lave is 7th generated planet in galaxy one
|
||||
const ubyte numforZaonce = 129
|
||||
const ubyte numforDiso = 147
|
||||
const ubyte numforRiedquat = 46
|
||||
uword num_commands
|
||||
|
||||
sub bench(uword max_time) -> uword {
|
||||
num_commands = 0
|
||||
txt.lowercase()
|
||||
cbm.SETTIM(0,0,0)
|
||||
while cbm.RDTIM16()<max_time {
|
||||
reinit()
|
||||
run_commands(max_time)
|
||||
}
|
||||
return num_commands
|
||||
}
|
||||
|
||||
sub reinit() {
|
||||
;txt.clear_screen()
|
||||
;txt.print("\n --- TextElite v1.3 ---\n")
|
||||
txt.print("\nnew game\n")
|
||||
elite_planet.set_seed(0, 0)
|
||||
elite_galaxy.travel_to(1, numforLave)
|
||||
elite_market.init(0) ; Lave's market is seeded with 0
|
||||
elite_ship.init()
|
||||
elite_planet.display(false, 0)
|
||||
input_index = 0
|
||||
}
|
||||
|
||||
sub run_commands(uword max_time) {
|
||||
while cbm.RDTIM16() < max_time {
|
||||
str input = "????????"
|
||||
;txt.print("\nCash: ")
|
||||
;elite_util.print_10s(elite_ship.cash)
|
||||
;txt.print("\nCommand (?=help): ")
|
||||
ubyte num_chars = next_input(input)
|
||||
;txt.nl()
|
||||
if num_chars!=0 {
|
||||
when input[0] {
|
||||
'q' -> {
|
||||
bool has_error = false
|
||||
if elite_galaxy.number != 2 {
|
||||
txt.print("\nERROR: galaxy is not 2: ")
|
||||
txt.print_ub(elite_galaxy.number)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_planet.number != 164 {
|
||||
txt.print("\nERROR: planet is not 164: ")
|
||||
txt.print_ub(elite_planet.number)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_planet.x != 116 {
|
||||
txt.print("\nERROR: planet.x is not 116: ")
|
||||
txt.print_ub(elite_planet.x)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_planet.y != 201 {
|
||||
txt.print("\nERROR: planet.y is not 201: ")
|
||||
txt.print_ub(elite_planet.y)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if "ribeen" != elite_planet.name {
|
||||
txt.print("\nERROR: planet.name is not 'ribeen': ")
|
||||
txt.print(elite_planet.name)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_ship.cash != 1212 {
|
||||
txt.print("\nERROR: cash is not 1212: ")
|
||||
txt.print_uw(elite_ship.cash)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_ship.fuel != 50 {
|
||||
txt.print("\nERROR: fuel is not 50:")
|
||||
txt.print_ub(elite_ship.fuel)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_ship.cargohold[0] != 3 {
|
||||
txt.print("\nERROR: food is not 3:")
|
||||
txt.print_ub(elite_ship.cargohold[0])
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_ship.cargohold[1] != 0 {
|
||||
txt.print("\nERROR: textiles is not 0:")
|
||||
txt.print_ub(elite_ship.cargohold[1])
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if has_error
|
||||
sys.exit(1)
|
||||
return
|
||||
}
|
||||
'b' -> elite_trader.do_buy()
|
||||
's' -> elite_trader.do_sell()
|
||||
'f' -> elite_trader.do_fuel()
|
||||
'j' -> elite_trader.do_jump()
|
||||
't' -> elite_trader.do_teleport()
|
||||
'g' -> elite_trader.do_next_galaxy()
|
||||
'i' -> elite_trader.do_info()
|
||||
'm' -> {
|
||||
if input[1]=='a' and input[2]=='p'
|
||||
elite_trader.do_map()
|
||||
else
|
||||
elite_trader.do_show_market()
|
||||
}
|
||||
'l' -> elite_trader.do_local()
|
||||
'c' -> elite_trader.do_cash()
|
||||
'h' -> elite_trader.do_hold()
|
||||
}
|
||||
num_commands++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str[] inputs = [
|
||||
"i",
|
||||
"diso",
|
||||
"i",
|
||||
"lave",
|
||||
"m",
|
||||
"b",
|
||||
"food",
|
||||
"15",
|
||||
"map",
|
||||
"g",
|
||||
"map",
|
||||
"l",
|
||||
"j",
|
||||
"zao",
|
||||
"s",
|
||||
"food",
|
||||
"12",
|
||||
"tele",
|
||||
"quti",
|
||||
"tele",
|
||||
"aro",
|
||||
"i",
|
||||
"diso",
|
||||
"i",
|
||||
"lave",
|
||||
"i",
|
||||
"zao",
|
||||
"galhyp",
|
||||
"fuel",
|
||||
"20",
|
||||
"j",
|
||||
"rib",
|
||||
"i",
|
||||
"rib",
|
||||
"i",
|
||||
"tiri",
|
||||
"q",
|
||||
0
|
||||
]
|
||||
|
||||
ubyte input_index
|
||||
|
||||
sub next_input(str buffer) -> ubyte {
|
||||
input_index++
|
||||
return string.copy(inputs[input_index], buffer)
|
||||
}
|
||||
}
|
||||
|
||||
elite_trader {
|
||||
str input = "??????????"
|
||||
ubyte num_chars
|
||||
|
||||
sub do_jump() {
|
||||
;txt.print("\nJump to what system? ")
|
||||
jump_to_system()
|
||||
}
|
||||
|
||||
sub do_teleport() {
|
||||
;txt.print("\nCheat! Teleport to what system? ")
|
||||
ubyte fuel = elite_ship.fuel
|
||||
elite_ship.fuel = 255
|
||||
jump_to_system()
|
||||
elite_ship.fuel = fuel
|
||||
}
|
||||
|
||||
sub jump_to_system() {
|
||||
void textelite.next_input(input)
|
||||
ubyte current_planet = elite_planet.number
|
||||
ubyte x = elite_planet.x
|
||||
ubyte y = elite_planet.y
|
||||
if elite_galaxy.search_closest_planet(input) {
|
||||
ubyte distance = elite_planet.distance(x, y)
|
||||
if distance <= elite_ship.fuel {
|
||||
elite_galaxy.init_market_for_planet()
|
||||
elite_ship.fuel -= distance
|
||||
;txt.print("\n\nHyperspace jump! Arrived at:\n")
|
||||
elite_planet.display(true,0 )
|
||||
return
|
||||
}
|
||||
;txt.print("\nInsufficient fuel\n")
|
||||
} else {
|
||||
;txt.print(" Not found!\n")
|
||||
}
|
||||
elite_galaxy.travel_to(elite_galaxy.number, current_planet)
|
||||
}
|
||||
|
||||
sub do_buy() {
|
||||
;txt.print("\nBuy what commodity? ")
|
||||
str commodity = "???????????????"
|
||||
void textelite.next_input(commodity)
|
||||
ubyte ci = elite_market.match(commodity)
|
||||
if ci & 128 !=0 {
|
||||
txt.print("Unknown\n")
|
||||
} else {
|
||||
;txt.print("\nHow much? ")
|
||||
void textelite.next_input(input)
|
||||
ubyte amount = conv.str2ubyte(input)
|
||||
if elite_market.current_quantity[ci] < amount {
|
||||
txt.print(" Insufficient supply!\n")
|
||||
} else {
|
||||
uword price = elite_market.current_price[ci] * amount
|
||||
;txt.print(" Total price: ")
|
||||
;elite_util.print_10s(price)
|
||||
if price > elite_ship.cash {
|
||||
txt.print(" Not enough cash!\n")
|
||||
} else {
|
||||
elite_ship.cash -= price
|
||||
elite_ship.cargohold[ci] += amount
|
||||
elite_market.current_quantity[ci] -= amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub do_sell() {
|
||||
;txt.print("\nSell what commodity? ")
|
||||
str commodity = "???????????????"
|
||||
void textelite.next_input(commodity)
|
||||
ubyte ci = elite_market.match(commodity)
|
||||
if ci & 128 !=0 {
|
||||
txt.print("Unknown\n")
|
||||
} else {
|
||||
;txt.print("\nHow much? ")
|
||||
void textelite.next_input(input)
|
||||
ubyte amount = conv.str2ubyte(input)
|
||||
if elite_ship.cargohold[ci] < amount {
|
||||
txt.print(" Insufficient supply!\n")
|
||||
} else {
|
||||
uword price = elite_market.current_price[ci] * amount
|
||||
;txt.print(" Total price: ")
|
||||
;elite_util.print_10s(price)
|
||||
elite_ship.cash += price
|
||||
elite_ship.cargohold[ci] -= amount
|
||||
elite_market.current_quantity[ci] += amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub do_fuel() {
|
||||
;txt.print("\nBuy fuel. Amount? ")
|
||||
void textelite.next_input(input)
|
||||
ubyte buy_fuel = 10*conv.str2ubyte(input)
|
||||
ubyte max_fuel = elite_ship.Max_fuel - elite_ship.fuel
|
||||
if buy_fuel > max_fuel
|
||||
buy_fuel = max_fuel
|
||||
uword price = buy_fuel as uword * elite_ship.Fuel_cost
|
||||
if price > elite_ship.cash {
|
||||
txt.print("Not enough cash!\n")
|
||||
} else {
|
||||
elite_ship.cash -= price
|
||||
elite_ship.fuel += buy_fuel
|
||||
}
|
||||
}
|
||||
|
||||
sub do_cash() {
|
||||
;txt.print("\nCheat! Set cash amount: ")
|
||||
void textelite.next_input(input)
|
||||
elite_ship.cash = conv.str2uword(input)
|
||||
}
|
||||
|
||||
sub do_hold() {
|
||||
;txt.print("\nCheat! Set cargohold size: ")
|
||||
void textelite.next_input(input)
|
||||
elite_ship.Max_cargo = conv.str2ubyte(input)
|
||||
}
|
||||
|
||||
sub do_next_galaxy() {
|
||||
txt.print("\n>>>>> Galaxy Hyperjump!\n")
|
||||
elite_galaxy.travel_to(elite_galaxy.number+1, elite_planet.number)
|
||||
elite_planet.display(false, 0)
|
||||
}
|
||||
|
||||
sub do_info() {
|
||||
;txt.print("\nSystem name (empty=current): ")
|
||||
num_chars = textelite.next_input(input)
|
||||
if num_chars!=0 {
|
||||
ubyte current_planet = elite_planet.number
|
||||
ubyte x = elite_planet.x
|
||||
ubyte y = elite_planet.y
|
||||
if elite_galaxy.search_closest_planet(input) {
|
||||
ubyte distance = elite_planet.distance(x, y)
|
||||
elite_planet.display(false, distance)
|
||||
} else {
|
||||
;txt.print(" Not found!")
|
||||
}
|
||||
elite_galaxy.travel_to(elite_galaxy.number, current_planet)
|
||||
} else {
|
||||
elite_planet.display(false, 0)
|
||||
}
|
||||
}
|
||||
|
||||
sub do_local() {
|
||||
elite_galaxy.local_area()
|
||||
}
|
||||
|
||||
sub do_map() {
|
||||
;txt.print("\n(l)ocal or (g)alaxy starmap? ")
|
||||
num_chars = textelite.next_input(input)
|
||||
if num_chars!=0 {
|
||||
elite_galaxy.starmap(input[0]=='l')
|
||||
}
|
||||
}
|
||||
|
||||
sub do_show_market() {
|
||||
elite_market.display()
|
||||
;txt.print("\nFuel: ")
|
||||
;elite_util.print_10s(elite_ship.fuel)
|
||||
;txt.print(" Cargohold space: ")
|
||||
;txt.print_ub(elite_ship.cargo_free())
|
||||
;txt.print("t\n")
|
||||
}
|
||||
}
|
||||
|
||||
elite_ship {
|
||||
const ubyte Max_fuel = 70
|
||||
const ubyte Fuel_cost = 2
|
||||
ubyte Max_cargo = 20
|
||||
|
||||
ubyte fuel
|
||||
uword cash
|
||||
ubyte[17] cargohold
|
||||
|
||||
sub init() {
|
||||
sys.memset(cargohold, len(cargohold), 0)
|
||||
fuel = Max_fuel
|
||||
cash = 1000
|
||||
}
|
||||
}
|
||||
|
||||
elite_market {
|
||||
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
|
||||
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
|
||||
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
|
||||
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
|
||||
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
|
||||
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
|
||||
|
||||
ubyte[17] current_quantity
|
||||
uword[17] current_price
|
||||
|
||||
sub init(ubyte fluct) {
|
||||
; Prices and availabilities are influenced by the planet's economy type
|
||||
; (0-7) and a random "fluctuation" byte that was kept within the saved
|
||||
; commander position to keep the market prices constant over gamesaves.
|
||||
; Availabilities must be saved with the game since the player alters them
|
||||
; by buying (and selling(?))
|
||||
;
|
||||
; Almost all commands are one byte only and overflow "errors" are
|
||||
; extremely frequent and exploited.
|
||||
;
|
||||
; Trade Item prices are held internally in a single byte=true value/4.
|
||||
; The decimal point in prices is introduced only when printing them.
|
||||
; Internally, all prices are integers.
|
||||
; The player's cash is held in four bytes.
|
||||
ubyte ci
|
||||
for ci in 0 to len(names)-1 {
|
||||
word product
|
||||
byte changing
|
||||
product = elite_planet.economy as word * gradients[ci]
|
||||
changing = fluct & maskbytes[ci] as byte
|
||||
ubyte q = (basequants[ci] as word + changing - product) as ubyte
|
||||
if q & $80 !=0
|
||||
q = 0 ; clip to positive 8-bit
|
||||
current_quantity[ci] = q & $3f
|
||||
q = (baseprices[ci] + changing + product) as ubyte
|
||||
current_price[ci] = q * $0004
|
||||
}
|
||||
current_quantity[16] = 0 ; force nonavailability of Alien Items
|
||||
}
|
||||
|
||||
sub display() {
|
||||
return
|
||||
; ubyte ci
|
||||
; txt.nl()
|
||||
; elite_planet.print_name_uppercase()
|
||||
; txt.print(" trade market:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
|
||||
; for ci in 0 to len(names)-1 {
|
||||
; elite_util.print_right(13, names[ci])
|
||||
; txt.print(" ")
|
||||
; elite_util.print_10s(current_price[ci])
|
||||
; txt.column(24)
|
||||
; txt.print_ub(current_quantity[ci])
|
||||
; txt.chrout(' ')
|
||||
; when units[ci] {
|
||||
; 0 -> txt.chrout('t')
|
||||
; 1 -> txt.print("kg")
|
||||
; 2 -> txt.chrout('g')
|
||||
; }
|
||||
; txt.column(32)
|
||||
; txt.print_ub(elite_ship.cargohold[ci])
|
||||
; txt.nl()
|
||||
; }
|
||||
}
|
||||
|
||||
sub match(uword nameptr) -> ubyte {
|
||||
ubyte ci
|
||||
for ci in 0 to len(names)-1 {
|
||||
if elite_util.prefix_matches(nameptr, names[ci])
|
||||
return ci
|
||||
}
|
||||
return 255
|
||||
}
|
||||
}
|
||||
|
||||
elite_galaxy {
|
||||
const uword GALSIZE = 256
|
||||
const uword base0 = $5A4A ; seeds for the first galaxy
|
||||
const uword base1 = $0248
|
||||
const uword base2 = $B753
|
||||
|
||||
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
|
||||
|
||||
ubyte number
|
||||
|
||||
uword[3] seed
|
||||
|
||||
sub init(ubyte galaxynum) {
|
||||
number = 1
|
||||
elite_planet.number = 255
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxynum-1 {
|
||||
nextgalaxy()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy() {
|
||||
textelite.num_commands++
|
||||
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
}
|
||||
|
||||
sub travel_to(ubyte galaxynum, ubyte system) {
|
||||
init(galaxynum)
|
||||
generate_next_planet() ; always at least planet 0 (separate to avoid repeat ubyte overflow)
|
||||
repeat system {
|
||||
generate_next_planet()
|
||||
textelite.num_commands++
|
||||
}
|
||||
elite_planet.name = make_current_planet_name()
|
||||
init_market_for_planet()
|
||||
}
|
||||
|
||||
sub init_market_for_planet() {
|
||||
elite_market.init(lsb(seed[0])+msb(seed[2]))
|
||||
}
|
||||
|
||||
sub search_closest_planet(uword nameptr) -> bool {
|
||||
textelite.num_commands++
|
||||
|
||||
ubyte x = elite_planet.x
|
||||
ubyte y = elite_planet.y
|
||||
ubyte current_planet_num = elite_planet.number
|
||||
|
||||
init(number)
|
||||
bool found = false
|
||||
ubyte current_closest_pi
|
||||
ubyte current_distance = 127
|
||||
ubyte pi
|
||||
for pi in 0 to 255 {
|
||||
generate_next_planet()
|
||||
elite_planet.name = make_current_planet_name()
|
||||
if elite_util.prefix_matches(nameptr, elite_planet.name) {
|
||||
ubyte distance = elite_planet.distance(x, y)
|
||||
if distance < current_distance {
|
||||
current_distance = distance
|
||||
current_closest_pi = pi
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found
|
||||
travel_to(number, current_closest_pi)
|
||||
else
|
||||
travel_to(number, current_planet_num)
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
sub local_area() {
|
||||
ubyte current_planet = elite_planet.number
|
||||
ubyte px = elite_planet.x
|
||||
ubyte py = elite_planet.y
|
||||
ubyte pn = 0
|
||||
|
||||
init(number)
|
||||
; txt.print("\nGalaxy #")
|
||||
; txt.print_ub(number)
|
||||
; txt.print(" - systems in vicinity:\n")
|
||||
do {
|
||||
generate_next_planet()
|
||||
ubyte distance = elite_planet.distance(px, py)
|
||||
if distance <= elite_ship.Max_fuel {
|
||||
; if distance <= elite_ship.fuel
|
||||
; txt.chrout('*')
|
||||
; else
|
||||
; txt.chrout('-')
|
||||
; txt.spc()
|
||||
elite_planet.name = make_current_planet_name()
|
||||
elite_planet.display(true, distance)
|
||||
}
|
||||
pn++
|
||||
} until pn==0
|
||||
|
||||
travel_to(number, current_planet)
|
||||
}
|
||||
|
||||
sub starmap(bool local) {
|
||||
ubyte current_planet = elite_planet.number
|
||||
ubyte px = elite_planet.x
|
||||
ubyte py = elite_planet.y
|
||||
str current_name = " " ; 8 max
|
||||
ubyte pn = 0
|
||||
|
||||
current_name = elite_planet.name
|
||||
init(number)
|
||||
; txt.clear_screen()
|
||||
; txt.print("Galaxy #")
|
||||
; txt.print_ub(number)
|
||||
; if local
|
||||
; txt.print(" - local systems")
|
||||
; else
|
||||
; txt.print(" - galaxy")
|
||||
; txt.print(" starmap:\n")
|
||||
ubyte max_distance = 255
|
||||
if local
|
||||
max_distance = elite_ship.Max_fuel
|
||||
ubyte home_sx
|
||||
ubyte home_sy
|
||||
ubyte home_distance
|
||||
|
||||
do {
|
||||
generate_next_planet()
|
||||
ubyte distance = elite_planet.distance(px, py)
|
||||
if distance <= max_distance {
|
||||
elite_planet.name = make_current_planet_name()
|
||||
elite_planet.name[0] = string.upperchar(elite_planet.name[0])
|
||||
uword tx = elite_planet.x
|
||||
uword ty = elite_planet.y
|
||||
if local {
|
||||
tx = tx + 24 - px
|
||||
ty = ty + 24 - py
|
||||
}
|
||||
ubyte sx = display_scale_x(tx)
|
||||
ubyte sy = display_scale_y(ty)
|
||||
ubyte char = '*'
|
||||
if elite_planet.number==current_planet
|
||||
char = '%'
|
||||
if local {
|
||||
print_planet_details(elite_planet.name, sx, sy, distance)
|
||||
} else if elite_planet.number==current_planet {
|
||||
home_distance = distance
|
||||
home_sx = sx
|
||||
home_sy = sy
|
||||
}
|
||||
; txt.setchr(2+sx, 2+sy, char)
|
||||
}
|
||||
pn++
|
||||
} until pn==0
|
||||
|
||||
if not local
|
||||
print_planet_details(current_name, home_sx, home_sy, home_distance)
|
||||
|
||||
; if local
|
||||
; txt.plot(0, display_scale_y(64) + 4)
|
||||
; else
|
||||
; txt.plot(0, display_scale_y(256) + 4 as ubyte)
|
||||
travel_to(number, current_planet)
|
||||
|
||||
sub print_planet_details(str name, ubyte screenx, ubyte screeny, ubyte d) {
|
||||
return
|
||||
; txt.plot(2+screenx-2, 2+screeny+1)
|
||||
; txt.print(name)
|
||||
; if d!=0 {
|
||||
; txt.plot(2+screenx-2, 2+screeny+2)
|
||||
; elite_util.print_10s(d)
|
||||
; txt.print(" LY")
|
||||
; }
|
||||
}
|
||||
|
||||
sub display_scale_x(uword x) -> ubyte {
|
||||
if local
|
||||
return x/2 as ubyte
|
||||
return x/8 as ubyte
|
||||
}
|
||||
|
||||
sub display_scale_y(uword y) -> ubyte {
|
||||
if local
|
||||
return y/4 as ubyte
|
||||
return y/16 as ubyte
|
||||
}
|
||||
}
|
||||
|
||||
ubyte pn_pair1
|
||||
ubyte pn_pair2
|
||||
ubyte pn_pair3
|
||||
ubyte pn_pair4
|
||||
bool longname
|
||||
|
||||
sub generate_next_planet() {
|
||||
determine_planet_properties()
|
||||
longname = lsb(seed[0]) & 64 !=0
|
||||
|
||||
; Always four iterations of random number
|
||||
pn_pair1 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
pn_pair2 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
pn_pair3 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
pn_pair4 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
}
|
||||
|
||||
sub make_current_planet_name() -> str {
|
||||
ubyte ni = 0
|
||||
str name = " " ; max 8
|
||||
|
||||
if pn_pairs[pn_pair1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair1]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair1+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair1+1]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair2] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair2]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair2+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair2+1]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair3] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair3]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair3+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair3+1]
|
||||
ni++
|
||||
}
|
||||
|
||||
if longname {
|
||||
if pn_pairs[pn_pair4] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair4]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair4+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair4+1]
|
||||
ni++
|
||||
}
|
||||
}
|
||||
|
||||
name[ni] = 0
|
||||
return name
|
||||
}
|
||||
|
||||
sub determine_planet_properties() {
|
||||
; create the planet's characteristics
|
||||
elite_planet.number++
|
||||
elite_planet.x = msb(seed[1])
|
||||
elite_planet.y = msb(seed[0])
|
||||
elite_planet.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
|
||||
elite_planet.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
|
||||
if elite_planet.govtype <= 1
|
||||
elite_planet.economy = (elite_planet.economy | 2)
|
||||
elite_planet.techlevel = (msb(seed[1]) & 3) + (elite_planet.economy ^ 7)
|
||||
elite_planet.techlevel += elite_planet.govtype >> 1
|
||||
if elite_planet.govtype & 1 !=0
|
||||
elite_planet.techlevel++
|
||||
elite_planet.population = 4 * elite_planet.techlevel + elite_planet.economy
|
||||
elite_planet.population += elite_planet.govtype + 1
|
||||
elite_planet.productivity = ((elite_planet.economy ^ 7) + 3) * (elite_planet.govtype + 4)
|
||||
elite_planet.productivity *= elite_planet.population * 8
|
||||
ubyte seed2_msb = msb(seed[2])
|
||||
elite_planet.radius = mkword((seed2_msb & 15) + 11, elite_planet.x)
|
||||
elite_planet.species_is_alien = lsb(seed[2]) & 128 !=0 ; bit 7 of w2_lo
|
||||
if elite_planet.species_is_alien {
|
||||
elite_planet.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
|
||||
elite_planet.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
|
||||
elite_planet.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
|
||||
elite_planet.species_kind = (elite_planet.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
elite_planet.goatsoup_seed[0] = lsb(seed[1])
|
||||
elite_planet.goatsoup_seed[1] = msb(seed[1])
|
||||
elite_planet.goatsoup_seed[2] = lsb(seed[2])
|
||||
elite_planet.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
||||
uword temp = seed[0] + seed[1] + seed[2]
|
||||
seed[0] = seed[1]
|
||||
seed[1] = seed[2]
|
||||
seed[2] = temp
|
||||
}
|
||||
|
||||
sub twist(uword x) -> uword {
|
||||
ubyte xh = msb(x)
|
||||
ubyte xl = lsb(x)
|
||||
xh <<= 1 ; make sure carry flag is not used on first shift!
|
||||
rol(xl)
|
||||
return mkword(xh, xl)
|
||||
}
|
||||
}
|
||||
|
||||
elite_planet {
|
||||
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
|
||||
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
|
||||
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
|
||||
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
|
||||
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
|
||||
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
|
||||
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
|
||||
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
|
||||
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
|
||||
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
|
||||
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
|
||||
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
|
||||
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
|
||||
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
|
||||
str[] words8F = ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"]
|
||||
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
|
||||
str[] words91 = ["planet", "world", "place", "little planet", "dump"]
|
||||
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
|
||||
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
|
||||
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
|
||||
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
|
||||
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
|
||||
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
|
||||
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
|
||||
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
|
||||
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
|
||||
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
|
||||
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
|
||||
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
|
||||
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
|
||||
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
|
||||
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
|
||||
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
|
||||
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
|
||||
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
|
||||
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
|
||||
|
||||
uword[] @shared wordlists = [
|
||||
words81, words82, words83, words84, words85, words86, words87, words88,
|
||||
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
|
||||
words91, words92, words93, words94, words95, words96, words97, words98,
|
||||
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
|
||||
wordsA1, wordsA2, wordsA3, wordsA4]
|
||||
|
||||
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
|
||||
|
||||
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
|
||||
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
|
||||
|
||||
str name = " " ; 8 max
|
||||
ubyte number ; starts at 0 in new galaxy, then increases by 1 for each generated planet
|
||||
ubyte x
|
||||
ubyte y
|
||||
ubyte economy
|
||||
ubyte govtype
|
||||
ubyte techlevel
|
||||
ubyte population
|
||||
uword productivity
|
||||
uword radius
|
||||
bool species_is_alien ; otherwise "Human Colonials"
|
||||
ubyte species_size
|
||||
ubyte species_color
|
||||
ubyte species_look
|
||||
ubyte species_kind
|
||||
|
||||
sub set_seed(uword s1, uword s2) {
|
||||
goatsoup_seed[0] = lsb(s1)
|
||||
goatsoup_seed[1] = msb(s1)
|
||||
goatsoup_seed[2] = lsb(s2)
|
||||
goatsoup_seed[3] = msb(s2)
|
||||
reset_rnd()
|
||||
}
|
||||
|
||||
sub reset_rnd() {
|
||||
goatsoup_rnd[0] = goatsoup_seed[0]
|
||||
goatsoup_rnd[1] = goatsoup_seed[1]
|
||||
goatsoup_rnd[2] = goatsoup_seed[2]
|
||||
goatsoup_rnd[3] = goatsoup_seed[3]
|
||||
}
|
||||
|
||||
sub random_name() -> str {
|
||||
ubyte ii
|
||||
str randname = " " ; 8 chars max
|
||||
ubyte nx = 0
|
||||
for ii in 0 to goatsoup_rnd_number() & 3 {
|
||||
ubyte xx = goatsoup_rnd_number() & $3e
|
||||
if pairs0[xx] != '.' {
|
||||
randname[nx] = pairs0[xx]
|
||||
nx++
|
||||
}
|
||||
xx++
|
||||
if pairs0[xx] != '.' {
|
||||
randname[nx] = pairs0[xx]
|
||||
nx++
|
||||
}
|
||||
}
|
||||
randname[nx] = 0
|
||||
randname[0] = string.upperchar(randname[0])
|
||||
return randname
|
||||
}
|
||||
|
||||
sub goatsoup_rnd_number() -> ubyte {
|
||||
ubyte xx = goatsoup_rnd[0] * 2
|
||||
uword a = xx as uword + goatsoup_rnd[2]
|
||||
if goatsoup_rnd[0] > 127
|
||||
a ++
|
||||
goatsoup_rnd[0] = lsb(a)
|
||||
goatsoup_rnd[2] = xx
|
||||
xx = goatsoup_rnd[1]
|
||||
ubyte ac = xx + goatsoup_rnd[3] + msb(a)
|
||||
goatsoup_rnd[1] = ac
|
||||
goatsoup_rnd[3] = xx
|
||||
return ac
|
||||
}
|
||||
|
||||
sub distance(ubyte px, ubyte py) -> ubyte {
|
||||
uword ax
|
||||
uword ay
|
||||
if px>x
|
||||
ax=px-x
|
||||
else
|
||||
ax=x-px
|
||||
if py>y
|
||||
ay=py-y
|
||||
else
|
||||
ay=y-py
|
||||
ay /= 2
|
||||
ubyte d = sqrt(ax*ax + ay*ay)
|
||||
if d>63
|
||||
return 255
|
||||
return d*4
|
||||
}
|
||||
|
||||
sub soup() -> str {
|
||||
str planet_result = " " * 160
|
||||
uword[6] source_stack
|
||||
ubyte stack_ptr = 0
|
||||
str start_source = "\x8F is \x97."
|
||||
uword source_ptr = &start_source
|
||||
uword result_ptr = &planet_result
|
||||
|
||||
reset_rnd()
|
||||
recursive_soup()
|
||||
return planet_result
|
||||
|
||||
sub recursive_soup() {
|
||||
repeat {
|
||||
ubyte c = @(source_ptr)
|
||||
source_ptr++
|
||||
if c == $00 {
|
||||
@(result_ptr) = 0
|
||||
return
|
||||
}
|
||||
else if c <= $80 {
|
||||
@(result_ptr) = c
|
||||
result_ptr++
|
||||
}
|
||||
else {
|
||||
if c <= $a4 {
|
||||
ubyte rnr = goatsoup_rnd_number()
|
||||
ubyte wordNr = ((rnr >= $33) as ubyte) + ((rnr >= $66) as ubyte) + ((rnr >= $99) as ubyte) + ((rnr >= $CC) as ubyte)
|
||||
source_stack[stack_ptr] = source_ptr
|
||||
stack_ptr++
|
||||
source_ptr = getword(c, wordNr)
|
||||
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
|
||||
stack_ptr--
|
||||
source_ptr = source_stack[stack_ptr]
|
||||
} else {
|
||||
if c == $b0 {
|
||||
@(result_ptr) = string.upperchar(name[0])
|
||||
result_ptr++
|
||||
concat_string(&name + 1)
|
||||
}
|
||||
else if c == $b1 {
|
||||
@(result_ptr) = string.upperchar(name[0])
|
||||
result_ptr++
|
||||
ubyte ni
|
||||
for ni in 1 to len(name) {
|
||||
ubyte cc = name[ni]
|
||||
if cc in ['e', 'o', 0]
|
||||
break
|
||||
else {
|
||||
@(result_ptr) = cc
|
||||
result_ptr++
|
||||
}
|
||||
}
|
||||
@(result_ptr) = 'i'
|
||||
result_ptr++
|
||||
@(result_ptr) = 'a'
|
||||
result_ptr++
|
||||
@(result_ptr) = 'n'
|
||||
result_ptr++
|
||||
}
|
||||
else if c == $b2 {
|
||||
concat_string(random_name())
|
||||
}
|
||||
else {
|
||||
@(result_ptr) = c
|
||||
result_ptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub concat_string(uword str_ptr) {
|
||||
repeat {
|
||||
ubyte c = @(str_ptr)
|
||||
if c==0
|
||||
break
|
||||
else {
|
||||
@(result_ptr) = c
|
||||
str_ptr++
|
||||
result_ptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub display(bool compressed, ubyte distance) {
|
||||
txt.print(soup())
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub getword(ubyte listnum, ubyte wordidx) -> uword {
|
||||
uword list = wordlists[listnum-$81]
|
||||
return peekw(list + wordidx*2)
|
||||
}
|
||||
}
|
||||
|
||||
elite_util {
|
||||
sub prefix_matches(uword prefixptr, uword stringptr) -> bool {
|
||||
repeat {
|
||||
ubyte pc = @(prefixptr)
|
||||
ubyte sc = @(stringptr)
|
||||
if pc == 0
|
||||
return true
|
||||
; to lowercase for case insensitive compare:
|
||||
if string.lowerchar(pc)!=string.lowerchar(sc)
|
||||
return false
|
||||
prefixptr++
|
||||
stringptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/compiled" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,112 @@
|
||||
|
||||
; This benchmark program is meant to check for regressions in the
|
||||
; Prog8 compiler's code-generator (performance wise).
|
||||
;
|
||||
; As the X16 computer is a more or less fixed system, it's not very useful
|
||||
; to benchmark the computer itself with.
|
||||
|
||||
|
||||
%import textio
|
||||
%import b_adpcm
|
||||
%import b_circles
|
||||
%import b_3d
|
||||
%import b_life
|
||||
%import b_mandelbrot
|
||||
%import b_queens
|
||||
%import b_textelite
|
||||
%import b_maze
|
||||
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
|
||||
main {
|
||||
|
||||
str[20] benchmark_names
|
||||
uword[20] benchmark_score
|
||||
|
||||
|
||||
sub start() {
|
||||
ubyte benchmark_number
|
||||
|
||||
void cx16.set_screen_mode(3)
|
||||
txt.color2(1, 6)
|
||||
txt.clear_screen()
|
||||
|
||||
txt.print("\n\n\n prog8 compiler benchmark tests.\n")
|
||||
sys.wait(60)
|
||||
|
||||
benchmark_number = 0
|
||||
|
||||
announce_benchmark("maze solver")
|
||||
benchmark_score[benchmark_number] = maze.bench(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("n-queens")
|
||||
benchmark_score[benchmark_number] = queens.bench(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("mandelbrot (floating point)")
|
||||
benchmark_score[benchmark_number] = mandelbrot.calc(400)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("game of life")
|
||||
benchmark_score[benchmark_number] = life.benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("3d model rotation")
|
||||
benchmark_score[benchmark_number] = rotate3d.benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("adpcm audio decoding")
|
||||
benchmark_score[benchmark_number] = adpcm.decode_benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("circles with gfx2")
|
||||
benchmark_score[benchmark_number] = circles.draw(false, 300)
|
||||
benchmark_number++
|
||||
|
||||
; announce_benchmark("circles with kernal")
|
||||
; benchmark_score[benchmark_number] = circles.draw(true, 300)
|
||||
; benchmark_number++
|
||||
|
||||
announce_benchmark("text-elite")
|
||||
benchmark_score[benchmark_number] = textelite.bench(120)
|
||||
benchmark_number++
|
||||
|
||||
benchmark_names[benchmark_number] = 0
|
||||
benchmark_score[benchmark_number] = 0
|
||||
|
||||
void cx16.set_screen_mode(3)
|
||||
txt.uppercase()
|
||||
txt.color2(1, 6)
|
||||
uword final_score
|
||||
benchmark_number = 0
|
||||
txt.print("\nscore benchmark\n\n")
|
||||
do {
|
||||
txt.spc()
|
||||
txt.print_uw(benchmark_score[benchmark_number])
|
||||
txt.column(6)
|
||||
txt.print(benchmark_names[benchmark_number])
|
||||
final_score += benchmark_score[benchmark_number]
|
||||
txt.nl()
|
||||
benchmark_number++
|
||||
} until benchmark_names[benchmark_number]==0
|
||||
|
||||
txt.print("\n\nfinal score : ")
|
||||
txt.print_uw(final_score)
|
||||
txt.nl()
|
||||
|
||||
sub announce_benchmark(str name) {
|
||||
benchmark_names[benchmark_number] = name
|
||||
void cx16.set_screen_mode(3)
|
||||
txt.uppercase()
|
||||
txt.color2(1, 6)
|
||||
txt.clear_screen()
|
||||
txt.plot(4, 6)
|
||||
txt.print(benchmark_names[benchmark_number])
|
||||
txt.nl()
|
||||
sys.wait(60)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -223,7 +222,7 @@ class StMemVar(name: String,
|
||||
init{
|
||||
require(dt!=DataType.BOOL && dt!=DataType.ARRAY_BOOL)
|
||||
if(dt in ArrayDatatypes || dt == DataType.STR)
|
||||
require(length!=null) { "memory mapped array or string must have known length" }
|
||||
requireNotNull(length)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ package prog8.code
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayDeque
|
||||
|
||||
|
||||
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
|
||||
fun make(): SymbolTable {
|
||||
@@ -13,8 +14,8 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
|
||||
}
|
||||
|
||||
val scopestack = Stack<StNode>()
|
||||
scopestack.push(st)
|
||||
val scopestack = ArrayDeque<StNode>()
|
||||
scopestack.add(st)
|
||||
program.children.forEach {
|
||||
addToSt(it, scopestack)
|
||||
}
|
||||
@@ -35,7 +36,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
return st
|
||||
}
|
||||
|
||||
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
|
||||
private fun addToSt(node: PtNode, scope: ArrayDeque<StNode>) {
|
||||
val stNode = when(node) {
|
||||
is PtAsmSub -> {
|
||||
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
|
||||
@@ -104,7 +105,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
val size = (node.args[1] as PtNumber).number.toUInt()
|
||||
val align = (node.args[2] as PtNumber).number.toUInt()
|
||||
// don't add memory slabs in nested scope, just put them in the top level of the ST
|
||||
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
|
||||
scope.first().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
|
||||
}
|
||||
null
|
||||
}
|
||||
@@ -112,14 +113,14 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
}
|
||||
|
||||
if(stNode!=null) {
|
||||
scope.peek().add(stNode)
|
||||
scope.push(stNode)
|
||||
scope.last().add(stNode)
|
||||
scope.add(stNode)
|
||||
}
|
||||
node.children.forEach {
|
||||
addToSt(it, scope)
|
||||
}
|
||||
if(stNode!=null)
|
||||
scope.pop()
|
||||
scope.removeLast()
|
||||
}
|
||||
|
||||
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.core.*
|
||||
import java.util.*
|
||||
import java.util.Objects
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.truncate
|
||||
|
||||
@@ -42,9 +42,20 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
else
|
||||
other.left isSameAs left && other.right isSameAs right
|
||||
}
|
||||
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
||||
is PtContainmentCheck -> {
|
||||
if(other !is PtContainmentCheck || other.type != type || !(other.needle isSameAs needle))
|
||||
false
|
||||
else {
|
||||
if(haystackHeapVar!=null)
|
||||
other.haystackHeapVar!=null && other.haystackHeapVar!! isSameAs haystackHeapVar!!
|
||||
else if(haystackValues!=null)
|
||||
other.haystackValues!=null && other.haystackValues!! isSameAs haystackValues!!
|
||||
else
|
||||
false
|
||||
}
|
||||
}
|
||||
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 +98,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 +150,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
|
||||
|
||||
@@ -199,10 +206,17 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
|
||||
|
||||
|
||||
class PtContainmentCheck(position: Position): PtExpression(DataType.BOOL, position) {
|
||||
val element: PtExpression
|
||||
val needle: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val iterable: PtIdentifier
|
||||
get() = children[1] as PtIdentifier
|
||||
val haystackHeapVar: PtIdentifier?
|
||||
get() = children[1] as? PtIdentifier
|
||||
val haystackValues: PtArray?
|
||||
get() = children[1] as? PtArray
|
||||
|
||||
companion object {
|
||||
val MAX_SIZE_FOR_INLINE_CHECKS_BYTE = 5
|
||||
val MAX_SIZE_FOR_INLINE_CHECKS_WORD = 4
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -210,11 +224,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 }
|
||||
}
|
||||
@@ -236,12 +245,6 @@ class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position)
|
||||
|
||||
|
||||
class PtBool(val value: Boolean, position: Position) : PtExpression(DataType.BOOL, position) {
|
||||
|
||||
companion object {
|
||||
fun fromNumber(number: Number, position: Position): PtBool =
|
||||
PtBool(if(number==0.0) false else true, position)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(type, value)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -276,12 +279,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)
|
||||
@@ -311,6 +314,7 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
|
||||
fun toConstantIntegerRange(): IntProgression? {
|
||||
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
return when {
|
||||
fromVal == toVal -> fromVal .. toVal
|
||||
fromVal <= toVal -> when {
|
||||
stepVal <= 0 -> IntRange.EMPTY
|
||||
stepVal == 1 -> fromVal..toVal
|
||||
@@ -354,7 +358,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
|
||||
|
||||
@@ -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"
|
||||
@@ -21,7 +21,16 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
else
|
||||
"&"
|
||||
}
|
||||
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
|
||||
is PtArray -> {
|
||||
val valuelist = node.children.map {
|
||||
when (it) {
|
||||
is PtNumber -> it.number.toString()
|
||||
is PtIdentifier -> it.name
|
||||
else -> "?"
|
||||
}
|
||||
}.joinToString(", ")
|
||||
"array len=${node.children.size} ${type(node.type)} [ $valuelist ]"
|
||||
}
|
||||
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
|
||||
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
|
||||
is PtBuiltinFunctionCall -> {
|
||||
@@ -34,7 +43,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 +72,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 +117,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()}"
|
||||
|
||||
@@ -5,6 +5,7 @@ import prog8.code.core.*
|
||||
|
||||
sealed interface IPtSubroutine {
|
||||
val name: String
|
||||
val scopedName: String
|
||||
}
|
||||
|
||||
class PtAsmSub(
|
||||
@@ -41,9 +42,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 +61,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 +79,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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -76,14 +76,14 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
// cmp returns a status in the carry flag, but not a proper return value
|
||||
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
||||
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
|
||||
"prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null),
|
||||
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
|
||||
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
|
||||
"prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
|
||||
"prog8_ifelse_bittest_notset" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
|
||||
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null),
|
||||
"abs__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||
"abs__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD),
|
||||
@@ -96,11 +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),
|
||||
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
|
||||
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
|
||||
"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),
|
||||
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
|
||||
@@ -130,7 +128,12 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"rrestore" to FSignature(false, emptyList(), null),
|
||||
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"callfar2" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("argA", arrayOf(DataType.UBYTE)), FParam("argX", arrayOf(DataType.UBYTE)), FParam("argY", arrayOf(DataType.UBYTE)), FParam("argC", arrayOf(DataType.BOOL))), DataType.UWORD),
|
||||
"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",
|
||||
"divmod", "divmod__ubyte", "divmod__uword"
|
||||
)
|
||||
@@ -20,13 +20,13 @@ 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,
|
||||
var slabsHighBank: Int? = null,
|
||||
var slabsGolden: Boolean = false,
|
||||
var splitWordArrays: Boolean = false,
|
||||
var strictBool: Boolean = true,
|
||||
var breakpointCpuInstruction: String? = null,
|
||||
var outputDir: Path = Path(""),
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package prog8.code.core
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
val powersOfTwoFloat = (0..16).map { (2.0).pow(it) }.toTypedArray()
|
||||
val negativePowersOfTwoFloat = powersOfTwoFloat.map { -it }.toTypedArray()
|
||||
val powersOfTwoInt = (0..16).map { 2.0.pow(it).toInt() }.toTypedArray()
|
||||
|
||||
fun Number.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
|
||||
@@ -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()} }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ interface IMachineDefinition {
|
||||
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun convertFloatToBytes(num: Double): List<UByte>
|
||||
fun convertBytesToFloat(bytes: List<UByte>): Double
|
||||
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
fun isIOAddress(address: UInt): Boolean
|
||||
}
|
||||
@@ -5,7 +5,11 @@ 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)
|
||||
KATAKANA("kata") // cx16 (katakana)
|
||||
}
|
||||
|
||||
interface IStringEncoding {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package prog8.code.core
|
||||
|
||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "and", "or", "xor")
|
||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are no longer associative because of Shortcircuit/McCarthy evaluation
|
||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
|
||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||
|
||||
@@ -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() &&
|
||||
(optimizeBitTest(program, options)
|
||||
+ optimizeAssignTargets(program, st, errors)) > 0
|
||||
) {
|
||||
// keep rolling
|
||||
}
|
||||
}
|
||||
@@ -22,109 +27,119 @@ private fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Boolean) {
|
||||
}
|
||||
|
||||
|
||||
private var tempVarCounter = 0
|
||||
|
||||
private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int {
|
||||
|
||||
fun extractableSubExpr(expr: PtExpression): Boolean {
|
||||
val result = if(expr is PtBinaryExpression)
|
||||
expr.type !in ByteDatatypes ||
|
||||
!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
|
||||
!expr.isSimple()
|
||||
return result
|
||||
}
|
||||
|
||||
// for each Binaryexpression, recurse to find a common subexpression pair therein.
|
||||
val commons = mutableMapOf<PtBinaryExpression, Pair<PtExpression, PtExpression>>()
|
||||
private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: IErrorReporter): Int {
|
||||
var changes = 0
|
||||
walkAst(program) { node: PtNode, depth: Int ->
|
||||
if(node is PtBinaryExpression) {
|
||||
val subExpressions = mutableListOf<PtExpression>()
|
||||
walkAst(node.left) { subNode: PtNode, subDepth: Int ->
|
||||
if (subNode is PtExpression) {
|
||||
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
|
||||
true
|
||||
} else false
|
||||
if(node is PtAssignment) {
|
||||
val value = node.value
|
||||
val functionName = when(value) {
|
||||
is PtBuiltinFunctionCall -> value.name
|
||||
is PtFunctionCall -> value.name
|
||||
else -> null
|
||||
}
|
||||
walkAst(node.right) { subNode: PtNode, subDepth: Int ->
|
||||
if (subNode is PtExpression) {
|
||||
if(extractableSubExpr(subNode)) subExpressions.add(subNode)
|
||||
true
|
||||
} else false
|
||||
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
|
||||
}
|
||||
|
||||
outer@for (first in subExpressions) {
|
||||
for (second in subExpressions) {
|
||||
if (first!==second && first isSameAs second) {
|
||||
commons[node] = first to second
|
||||
break@outer // do only 1 replacement at a time per binaryexpression
|
||||
|
||||
private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): Int {
|
||||
if(options.compTarget.machine.cpu == CpuType.VIRTUAL)
|
||||
return 0 // the special bittest optimization is not yet valid for the IR
|
||||
|
||||
var changes = 0
|
||||
var recurse = true
|
||||
walkAst(program) { node: PtNode, depth: Int ->
|
||||
if(node is PtIfElse) {
|
||||
val condition = node.condition as? PtBinaryExpression
|
||||
if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) {
|
||||
if(condition.right.asConstInteger()==0) {
|
||||
val and = condition.left as? PtBinaryExpression
|
||||
if(and != null && and.operator=="&" && and.type == DataType.UBYTE) {
|
||||
val variable = and.left as? PtIdentifier
|
||||
val bitmask = and.right.asConstInteger()
|
||||
if(variable!=null && variable.type in ByteDatatypes && (bitmask==128 || bitmask==64)) {
|
||||
val setOrNot = if(condition.operator=="!=") "set" else "notset"
|
||||
val index = node.parent.children.indexOf(node)
|
||||
val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, node.condition.position)
|
||||
bittestCall.add(variable)
|
||||
if(bitmask==128)
|
||||
bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position))
|
||||
else
|
||||
bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position))
|
||||
val ifElse = PtIfElse(node.position)
|
||||
ifElse.add(bittestCall)
|
||||
ifElse.add(node.ifScope)
|
||||
if(node.hasElse())
|
||||
ifElse.add(node.elseScope)
|
||||
node.parent.children[index] = ifElse
|
||||
ifElse.parent = node.parent
|
||||
changes++
|
||||
recurse = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
} else true
|
||||
}
|
||||
|
||||
// replace common subexpressions by a temp variable that is assigned only once.
|
||||
// TODO: check for commonalities across multiple separate expressions in the current scope, not only inside a single line
|
||||
commons.forEach { binexpr, (occurrence1, occurrence2) ->
|
||||
val (stmtContainer, stmt) = findContainingStatements(binexpr)
|
||||
val occurrence1idx = occurrence1.parent.children.indexOf(occurrence1)
|
||||
val occurrence2idx = occurrence2.parent.children.indexOf(occurrence2)
|
||||
val containerScopedName = findScopeName(stmtContainer)
|
||||
tempVarCounter++
|
||||
val tempvarName = "prog8_subexprvar_$tempVarCounter"
|
||||
// TODO: some tempvars could be reused, if they are from different lines
|
||||
|
||||
val datatype = occurrence1.type
|
||||
val singleReplacement1 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence1.position)
|
||||
val singleReplacement2 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence2.position)
|
||||
occurrence1.parent.children[occurrence1idx] = singleReplacement1
|
||||
singleReplacement1.parent = occurrence1.parent
|
||||
occurrence2.parent.children[occurrence2idx] = singleReplacement2
|
||||
singleReplacement2.parent = occurrence2.parent
|
||||
|
||||
val tempassign = PtAssignment(binexpr.position).also { assign ->
|
||||
assign.add(PtAssignTarget(binexpr.position).also { tgt->
|
||||
tgt.add(PtIdentifier("$containerScopedName.$tempvarName", datatype, binexpr.position))
|
||||
})
|
||||
assign.add(occurrence1)
|
||||
occurrence1.parent = assign
|
||||
}
|
||||
stmtContainer.children.add(stmtContainer.children.indexOf(stmt), tempassign)
|
||||
tempassign.parent = stmtContainer
|
||||
|
||||
val tempvar = PtVariable(tempvarName, datatype, ZeropageWish.NOT_IN_ZEROPAGE, null, null, binexpr.position)
|
||||
stmtContainer.add(0, tempvar)
|
||||
tempvar.parent = stmtContainer
|
||||
|
||||
// errors.info("common subexpressions replaced by a tempvar, maybe simplify the expression manually", binexpr.position)
|
||||
recurse
|
||||
}
|
||||
|
||||
return commons.size
|
||||
return changes
|
||||
}
|
||||
|
||||
|
||||
internal fun findScopeName(node: PtNode): String {
|
||||
var parent=node
|
||||
while(parent !is PtNamedNode)
|
||||
parent = parent.parent
|
||||
return parent.scopedName
|
||||
}
|
||||
|
||||
|
||||
internal fun findContainingStatements(node: PtNode): Pair<PtNode, PtNode> { // returns (parentstatementcontainer, childstatement)
|
||||
var parent = node.parent
|
||||
var child = node
|
||||
while(true) {
|
||||
if(parent is IPtStatementContainer) {
|
||||
return parent to child
|
||||
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")
|
||||
}
|
||||
}
|
||||
child=parent
|
||||
parent=parent.parent
|
||||
}
|
||||
return false // there are no identifiers directly corresponding to cpu registers
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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,10 @@ 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)
|
||||
Encoding.KATAKANA -> KatakanaEncoding.encode(str)
|
||||
else -> throw InternalCompilerException("unsupported encoding $encoding")
|
||||
}
|
||||
return coded.fold(
|
||||
@@ -31,6 +33,10 @@ 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)
|
||||
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes)
|
||||
else -> throw InternalCompilerException("unsupported encoding $encoding")
|
||||
}
|
||||
return decoded.fold(
|
||||
|
||||
@@ -22,13 +22,8 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.output == OutputType.XEX)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
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 launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
val emulatorName: String
|
||||
|
||||
@@ -25,11 +25,15 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
override fun 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 launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
|
||||
@@ -26,11 +26,15 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
override fun 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 launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
|
||||
@@ -24,11 +24,16 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
|
||||
override fun 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 launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
|
||||
+1
-1
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-3
@@ -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")
|
||||
@@ -0,0 +1,122 @@
|
||||
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 JapaneseCharacterConverter {
|
||||
// adapted from https://github.com/raminduw/Japanese-Character-Converter
|
||||
|
||||
private val ZENKAKU_KATAKANA = charArrayOf(
|
||||
'ァ', 'ア', 'ィ', 'イ', 'ゥ',
|
||||
'ウ', 'ェ', 'エ', 'ォ', 'オ', 'カ', 'ガ', 'キ', 'ギ', 'ク', 'グ', 'ケ', 'ゲ',
|
||||
'コ', 'ゴ', 'サ', 'ザ', 'シ', 'ジ', 'ス', 'ズ', 'セ', 'ゼ', 'ソ', 'ゾ', 'タ',
|
||||
'ダ', 'チ', 'ヂ', 'ッ', 'ツ', 'ヅ', 'テ', 'デ', 'ト', 'ド', 'ナ', 'ニ', 'ヌ',
|
||||
'ネ', 'ノ', 'ハ', 'バ', 'パ', 'ヒ', 'ビ', 'ピ', 'フ', 'ブ', 'プ', 'ヘ', 'ベ',
|
||||
'ペ', 'ホ', 'ボ', 'ポ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ャ', 'ヤ', 'ュ', 'ユ',
|
||||
'ョ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ヮ', 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン',
|
||||
'ヴ', 'ヵ', 'ヶ'
|
||||
)
|
||||
|
||||
private val HANKAKU_HIRAGANA = charArrayOf(
|
||||
'ぁ', 'あ', 'ぃ', 'い', 'ぅ', 'う', 'ぇ', 'え',
|
||||
'ぉ', 'お', 'か', 'が', 'き', 'ぎ', 'く', 'ぐ',
|
||||
'け', 'げ', 'こ', 'ご', 'さ', 'ざ', 'し', 'じ',
|
||||
'す', 'ず', 'せ', 'ぜ', 'そ', 'ぞ', 'た', 'だ',
|
||||
'ち', 'ぢ', 'っ', 'つ', 'づ', 'て', 'で', 'と',
|
||||
'ど', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ば',
|
||||
'ぱ', 'ひ', 'び', 'ぴ', 'ふ', 'ぶ', 'ぷ', 'へ',
|
||||
'べ', 'ぺ', 'ほ', 'ぼ', 'ぽ', 'ま', 'み', 'む',
|
||||
'め', 'も', 'ゃ', 'や', 'ゅ', 'ゆ', 'ょ', 'よ',
|
||||
'ら', 'り', 'る', 'れ', 'ろ', 'ゎ', 'わ', 'ゐ',
|
||||
'ゑ', 'を', 'ん', 'ゔ', 'ゕ', 'ゖ'
|
||||
)
|
||||
|
||||
private val HANKAKU_KATAKANA = arrayOf(
|
||||
"ァ", "ア", "ィ", "イ", "ゥ",
|
||||
"ウ", "ェ", "エ", "ォ", "オ", "カ", "ガ", "キ", "ギ", "ク", "グ", "ケ",
|
||||
"ゲ", "コ", "ゴ", "サ", "ザ", "シ", "ジ", "ス", "ズ", "セ", "ゼ", "ソ",
|
||||
"ゾ", "タ", "ダ", "チ", "ヂ", "ッ", "ツ", "ヅ", "テ", "デ", "ト", "ド",
|
||||
"ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "バ", "パ", "ヒ", "ビ", "ピ", "フ",
|
||||
"ブ", "プ", "ヘ", "ベ", "ペ", "ホ", "ボ", "ポ", "マ", "ミ", "ム", "メ",
|
||||
"モ", "ャ", "ヤ", "ュ", "ユ", "ョ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ",
|
||||
"ワ", "イ", "エ", "ヲ", "ン", "ヴ", "カ", "ケ"
|
||||
)
|
||||
|
||||
private val ZENKAKU_KATAKANA_FIRST_CHAR_CODE = ZENKAKU_KATAKANA.first().code
|
||||
private val HANKAKU_HIRAGANA_FIRST_CHAR_CODE = HANKAKU_HIRAGANA.first().code
|
||||
|
||||
private fun zenkakuKatakanaToHankakuKatakana(c: Char): String = if (c in ZENKAKU_KATAKANA) HANKAKU_KATAKANA[c.code - ZENKAKU_KATAKANA_FIRST_CHAR_CODE] else c.toString()
|
||||
private fun hankakuKatakanaToZenkakuKatakana(c: Char): Char = if (c in HANKAKU_HIRAGANA) ZENKAKU_KATAKANA[c.code - HANKAKU_HIRAGANA_FIRST_CHAR_CODE] else c
|
||||
|
||||
fun zenkakuKatakanaToHankakuKatakana(s: String): String = buildString {
|
||||
for (element in s) {
|
||||
val converted = hankakuKatakanaToZenkakuKatakana(element)
|
||||
val convertedChar = zenkakuKatakanaToHankakuKatakana(converted)
|
||||
append(convertedChar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object KatakanaEncoding {
|
||||
val charset: Charset = Charset.forName("JIS_X0201")
|
||||
|
||||
fun encode(str: String): Result<List<UByte>, CharConversionException> {
|
||||
return try {
|
||||
val mapped = str.map { chr ->
|
||||
when (chr) {
|
||||
|
||||
'\u0000' -> 0u
|
||||
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves
|
||||
|
||||
'♥' -> 0xe3u
|
||||
'♦' -> 0xe4u
|
||||
'♣' -> 0xe5u
|
||||
'♠' -> 0xe6u
|
||||
|
||||
'大' -> 0xeau
|
||||
'中' -> 0xebu
|
||||
'小' -> 0xecu
|
||||
'百' -> 0xedu
|
||||
'千' -> 0xeeu
|
||||
'万' -> 0xefu
|
||||
'♪' -> 0xf0u
|
||||
'土' -> 0xf1u
|
||||
'金' -> 0xf2u
|
||||
'木' -> 0xf3u
|
||||
'水' -> 0xf4u
|
||||
'火' -> 0xf5u
|
||||
'月' -> 0xf6u
|
||||
'日' -> 0xf7u
|
||||
'時' -> 0xf8u
|
||||
'分' -> 0xf9u
|
||||
'秒' -> 0xfau
|
||||
'年' -> 0xfbu
|
||||
'円' -> 0xfcu
|
||||
'人' -> 0xfdu
|
||||
'生' -> 0xfeu
|
||||
'〒' -> 0xffu
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
@@ -25,11 +25,15 @@ class PETMachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
override fun 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 launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
|
||||
@@ -30,8 +30,24 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
return parts.joinToString(", ")
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return listOf("syslib")
|
||||
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 launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
|
||||
@@ -5,9 +5,8 @@ plugins {
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
targetCompatibility = JavaLanguageVersion.of(javaVersion)
|
||||
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
@@ -26,9 +25,10 @@ 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.1'
|
||||
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -9,7 +9,7 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.codegen.cpu6502.assignment.*
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayDeque
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeLines
|
||||
|
||||
@@ -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,12 +100,11 @@ 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)
|
||||
is PtVariable -> parent.children[index] = node.prefix(st)
|
||||
is PtVariable -> parent.children[index] = node.prefix(parent, st)
|
||||
else -> throw AssemblyError("weird node to prefix $node")
|
||||
}
|
||||
}
|
||||
@@ -136,7 +135,7 @@ private fun prefixScopedName(name: String, type: Char): String {
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
|
||||
private fun PtVariable.prefix(st: SymbolTable): PtVariable {
|
||||
private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
name = prefixScopedName(name, 'v')
|
||||
if(value==null)
|
||||
return this
|
||||
@@ -164,7 +163,9 @@ private fun PtVariable.prefix(st: SymbolTable): PtVariable {
|
||||
else -> throw AssemblyError("weird array value element $elt")
|
||||
}
|
||||
}
|
||||
PtVariable(name, type, zeropage, newValue, arraySize, position)
|
||||
val result = PtVariable(name, type, zeropage, newValue, arraySize, position)
|
||||
result.parent = parent
|
||||
result
|
||||
}
|
||||
else this
|
||||
}
|
||||
@@ -233,7 +234,7 @@ class AsmGen6502Internal (
|
||||
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
|
||||
private val programGen = ProgramAndVarsGen(program, options, errors, symbolTable, functioncallAsmGen, this, allocator, zeropage)
|
||||
private val anyExprGen = AnyExprAsmGen(this)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this, options, anyExprGen, allocator)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator)
|
||||
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
|
||||
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, allocator, assignmentAsmGen, errors)
|
||||
|
||||
@@ -247,7 +248,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()) {
|
||||
@@ -375,7 +376,7 @@ class AsmGen6502Internal (
|
||||
val name = asmSymbolName(identifier.name)
|
||||
|
||||
// see if we're inside a subroutine, if so, remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
|
||||
val subName = identifier.definingSub()?.scopedName
|
||||
val subName = identifier.definingISub()?.scopedName
|
||||
return if (subName != null && name.length>subName.length && name.startsWith(subName) && name[subName.length] == '.')
|
||||
name.drop(subName.length + 1)
|
||||
else
|
||||
@@ -386,14 +387,14 @@ class AsmGen6502Internal (
|
||||
val name = asmVariableName(identifier.name)
|
||||
|
||||
// see if we're inside a subroutine, if so, remove the whole prefix and just make the variable name locally scoped (64tass scopes it to the proper .proc block)
|
||||
val subName = identifier.definingSub()?.scopedName
|
||||
val subName = identifier.definingISub()?.scopedName
|
||||
return if (subName != null && name.length>subName.length && name.startsWith(subName) && name[subName.length] == '.')
|
||||
name.drop(subName.length+1)
|
||||
else
|
||||
name
|
||||
}
|
||||
|
||||
fun asmVariableName(st: StNode, scope: PtSub?): String {
|
||||
fun asmVariableName(st: StNode, scope: IPtSubroutine?): String {
|
||||
val name = asmVariableName(st.scopedName)
|
||||
if(scope==null)
|
||||
return name
|
||||
@@ -587,7 +588,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)
|
||||
@@ -770,7 +774,7 @@ class AsmGen6502Internal (
|
||||
|
||||
private fun translate(stmt: PtRepeatLoop) {
|
||||
val endLabel = makeLabel("repeatend")
|
||||
loopEndLabels.push(endLabel)
|
||||
loopEndLabels.add(endLabel)
|
||||
|
||||
when (stmt.count) {
|
||||
is PtNumber -> {
|
||||
@@ -814,20 +818,18 @@ class AsmGen6502Internal (
|
||||
}
|
||||
}
|
||||
|
||||
loopEndLabels.pop()
|
||||
loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
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 +882,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 +1319,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) {
|
||||
|
||||
@@ -50,6 +50,13 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeTSBtoRegularOr(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
var linesByFourteen = getLinesBy(lines, 14)
|
||||
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
|
||||
if(mods.isNotEmpty()) {
|
||||
@@ -65,8 +72,6 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
// TODO more assembly peephole optimizations
|
||||
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
@@ -386,6 +391,7 @@ private fun optimizeStoreLoadSame(
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[1].value.trimStart()
|
||||
val second = lines[2].value.trimStart()
|
||||
val third = lines[3].value.trimStart()
|
||||
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
|
||||
@@ -395,7 +401,6 @@ private fun optimizeStoreLoadSame(
|
||||
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("ldx ") && second.startsWith("ldx "))
|
||||
) {
|
||||
val third = lines[3].value.trimStart()
|
||||
val attemptRemove =
|
||||
if(third.isBranch()) {
|
||||
// a branch instruction follows, we can only remove the load instruction if
|
||||
@@ -510,34 +515,36 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[0].value
|
||||
val second = lines[1].value
|
||||
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
else if (" rts" in first || "\trts" in first) {
|
||||
if (" jmp" in second || "\tjmp" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bra" in second || "\tbra" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bcc" in second || "\tbcc" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bcs" in second || "\tbcs" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" beq" in second || "\tbeq" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bne" in second || "\tbne" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bmi" in second || "\tbmi" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bpl" in second || "\tbpl" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bvs" in second || "\tbvs" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bvc" in second || "\tbvc" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
val third = lines[2].value
|
||||
|
||||
if(!haslabel(second)) {
|
||||
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
else if (" rts" in first || "\trts" in first) {
|
||||
if (" jmp" in second || "\tjmp" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bra" in second || "\tbra" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bcc" in second || "\tbcc" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bcs" in second || "\tbcs" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" beq" in second || "\tbeq" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bne" in second || "\tbne" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bmi" in second || "\tbmi" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bpl" in second || "\tbpl" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bvs" in second || "\tbvs" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
else if (" bvc" in second || "\tbvc" in second)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
|
||||
if (!haslabel(second)) {
|
||||
if ((" lda" in first || "\tlda" in first) && (" cmp #0" in second || "\tcmp #0" in second) ||
|
||||
(" ldx" in first || "\tldx" in first) && (" cpx #0" in second || "\tcpx #0" in second) ||
|
||||
(" ldy" in first || "\tldy" in first) && (" cpy #0" in second || "\tcpy #0" in second)
|
||||
@@ -579,6 +586,58 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
|
||||
mods += Modification(lines[3].index, true, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun sameLabel(branchInstr: String, jumpInstr: String, labelInstr: String): Boolean {
|
||||
if('(' in jumpInstr) return false // indirect jump cannot be replaced
|
||||
val label = labelInstr.trimEnd().substringBefore(':').substringBefore(' ').substringBefore('\t')
|
||||
val branchLabel = branchInstr.trimStart().substring(3).trim()
|
||||
return label==branchLabel
|
||||
}
|
||||
|
||||
// beq Label + jmp Addr + Label -> bne Addr
|
||||
if((" jmp" in second || "\tjmp " in second) && haslabel(third)) {
|
||||
if((" beq " in first || "\tbeq " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bne")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bne " in first || "\tbne " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "beq")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bcc " in first || "\tbcc " in first) && sameLabel(first, second, third)){
|
||||
val branch = second.replace("jmp", "bcs")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bcs " in first || "\tbcs " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bcc")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bpl " in first || "\tbpl " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bmi")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bmi " in first || "\tbmi " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bpl")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bvc " in first || "\tbvc " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bvs")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bvs " in first || "\tbvs " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bvc")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
@@ -616,6 +675,52 @@ private fun optimizeUselessPushPopStack(linesByFour: Sequence<List<IndexedValue<
|
||||
optimize('a', lines)
|
||||
optimize('x', lines)
|
||||
optimize('y', lines)
|
||||
|
||||
val first = lines[1].value.trimStart()
|
||||
val second = lines[2].value.trimStart()
|
||||
val third = lines[3].value.trimStart()
|
||||
|
||||
// phy + ldy + pla -> tya + ldy
|
||||
// phx + ldx + pla -> txa + ldx
|
||||
// pha + lda + pla -> nop
|
||||
if(first=="phy" && second.startsWith("ldy ") && third=="pla") {
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, " tya"))
|
||||
}
|
||||
else if(first=="phx" && second.startsWith("ldx ") && third=="pla") {
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, " txa"))
|
||||
}
|
||||
else if(first=="pha" && second.startsWith("lda ") && third=="pla") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
|
||||
private fun optimizeTSBtoRegularOr(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
|
||||
// Asm peephole: lda var2 / tsb var1 / lda var1 Replace this with this to save 1 cycle: lda var1 / ora var2 / sta var1
|
||||
val mods = mutableListOf<Modification>()
|
||||
|
||||
for(lines in linesByFour) {
|
||||
val first = lines[0].value.trimStart()
|
||||
val second = lines[1].value.trimStart()
|
||||
val third = lines[2].value.trimStart()
|
||||
if(first.startsWith("lda") && second.startsWith("tsb") && third.startsWith("lda")) {
|
||||
val operand1 = first.substring(3)
|
||||
val operand2 = second.substring(3)
|
||||
val operand3 = third.substring(3)
|
||||
if(operand1!=operand2 && operand2==operand3) {
|
||||
mods.add(Modification(lines[0].index, false, " lda $operand2"))
|
||||
mods.add(Modification(lines[1].index, false, " ora $operand1"))
|
||||
mods.add(Modification(lines[2].index, false, " sta $operand2"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -33,7 +33,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(fcall, resultRegister)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(fcall, resultRegister)
|
||||
"abs__byte", "abs__word", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, resultRegister, sscope)
|
||||
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(fcall, resultRegister, sscope)
|
||||
"divmod__ubyte" -> funcDivmod(fcall)
|
||||
@@ -44,8 +43,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"ror2" -> funcRor2(fcall)
|
||||
"setlsb" -> funcSetLsbMsb(fcall, false)
|
||||
"setmsb" -> funcSetLsbMsb(fcall, true)
|
||||
"sort" -> funcSort(fcall)
|
||||
"reverse" -> funcReverse(fcall)
|
||||
"memory" -> funcMemory(fcall, discardResult, resultRegister)
|
||||
"peekw" -> funcPeekW(fcall, resultRegister)
|
||||
"peekf" -> funcPeekF(fcall, resultRegister)
|
||||
@@ -67,7 +64,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"rrestore" -> funcRrestore()
|
||||
"cmp" -> funcCmp(fcall)
|
||||
"callfar" -> funcCallFar(fcall, resultRegister)
|
||||
"callfar2" -> funcCallFar2(fcall, resultRegister)
|
||||
"call" -> funcCall(fcall)
|
||||
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
|
||||
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
|
||||
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
|
||||
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
|
||||
@@ -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
|
||||
@@ -268,33 +267,110 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun funcCall(fcall: PtBuiltinFunctionCall) {
|
||||
// note: the routine can return a word value (in AY)
|
||||
val constAddr = fcall.args[0].asConstInteger()
|
||||
if(constAddr!=null) {
|
||||
asmgen.out(" jsr ${constAddr.toHex()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY) // jump address
|
||||
asmgen.out("""
|
||||
sta (+)+1
|
||||
sty (+)+2
|
||||
+ jsr 0 ; modified""")
|
||||
return
|
||||
}
|
||||
|
||||
// note: the routine can return a word value (in AY)
|
||||
val identifier = fcall.args[0] as? PtIdentifier
|
||||
if(identifier!=null) {
|
||||
asmgen.out("""
|
||||
; push a return address so the jmp becomes indirect jsr
|
||||
lda #>((+)-1)
|
||||
pha
|
||||
lda #<((+)-1)
|
||||
pha
|
||||
jmp (${asmgen.asmSymbolName(identifier)})
|
||||
+""")
|
||||
return
|
||||
}
|
||||
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD) // jump address
|
||||
asmgen.out("""
|
||||
; push a return address so the jmp becomes indirect jsr
|
||||
lda #>((+)-1)
|
||||
pha
|
||||
lda #<((+)-1)
|
||||
pha
|
||||
jmp (P8ZP_SCRATCH_W2)
|
||||
+""")
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
|
||||
asmgen.out(" sta (++)+0")
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1")
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
val constBank = fcall.args[0].asConstInteger()
|
||||
val constAddress = fcall.args[1].asConstInteger()
|
||||
if(constBank!=null && constAddress!=null) {
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
.word ${constAddress.toHex()}
|
||||
.byte $constBank""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
|
||||
asmgen.out(" sta (++)+0")
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1")
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
}
|
||||
|
||||
// note that by convention the values in A+Y registers are now the return value of the call.
|
||||
if(resultRegister!=null) {
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCallFar2(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callfar2 only works on cx16 target at this time")
|
||||
|
||||
fun assignArgs() {
|
||||
fun assign(value: PtExpression, register: Char) {
|
||||
when(value) {
|
||||
is PtBool -> asmgen.out(" ld$register #${value.asInt()}")
|
||||
is PtNumber -> asmgen.out(" ld$register #${value.number.toInt()}")
|
||||
is PtIdentifier -> asmgen.out(" ld$register ${asmgen.asmVariableName(value)}")
|
||||
else -> TODO("callfar2: support non-simple expressions for arguments")
|
||||
}
|
||||
}
|
||||
assign(fcall.args[2], 'a')
|
||||
assign(fcall.args[3], 'x')
|
||||
assign(fcall.args[4], 'y')
|
||||
val carry = fcall.args[5].asConstInteger()
|
||||
if(carry!=null)
|
||||
asmgen.out(if(carry==0) " clc" else " sec")
|
||||
else
|
||||
TODO("callfar2: support non-const argument values")
|
||||
}
|
||||
|
||||
val constBank = fcall.args[0].asConstInteger()
|
||||
val constAddress = fcall.args[1].asConstInteger()
|
||||
if(constBank!=null && constAddress!=null) {
|
||||
assignArgs()
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
.word ${constAddress.toHex()}
|
||||
.byte $constBank""")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
|
||||
asmgen.out(" sta (++)+0")
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1")
|
||||
assignArgs()
|
||||
asmgen.out("""
|
||||
jsr cx16.JSRFAR
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
}
|
||||
|
||||
// note that by convention the values in A+Y registers are now the return value of the call.
|
||||
if(resultRegister!=null) {
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
|
||||
@@ -404,115 +480,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcReverse(fcall: PtBuiltinFunctionCall) {
|
||||
val variable = fcall.args.single() as PtIdentifier
|
||||
val symbol = asmgen.symbolTable.lookup(variable.name)
|
||||
val (dt, numElements) = when(symbol) {
|
||||
is StStaticVariable -> symbol.dt to symbol.length!!
|
||||
is StMemVar -> symbol.dt to symbol.length!!
|
||||
else -> DataType.UNDEFINED to 0
|
||||
}
|
||||
val varName = asmgen.asmVariableName(variable)
|
||||
when (dt) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_b""")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_w""")
|
||||
}
|
||||
DataType.STR -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #${numElements-1}
|
||||
jsr prog8_lib.func_reverse_b""")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr floats.func_reverse_f""")
|
||||
}
|
||||
in SplitWordArrayTypes -> {
|
||||
// reverse the lsb and msb arrays both, independently
|
||||
asmgen.out("""
|
||||
lda #<${varName}_lsb
|
||||
ldy #>${varName}_lsb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_b
|
||||
lda #<${varName}_msb
|
||||
ldy #>${varName}_msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_b""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSort(fcall: PtBuiltinFunctionCall) {
|
||||
val variable = fcall.args.single() as PtIdentifier
|
||||
val symbol = asmgen.symbolTable.lookup(variable.name)
|
||||
val varName = asmgen.asmVariableName(variable)
|
||||
val (dt, numElements) = when(symbol) {
|
||||
is StStaticVariable -> symbol.dt to symbol.length!!
|
||||
is StMemVar -> symbol.dt to symbol.length!!
|
||||
else -> DataType.UNDEFINED to 0
|
||||
}
|
||||
when (dt) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements""")
|
||||
asmgen.out(if (dt == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements""")
|
||||
asmgen.out(if (dt == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
|
||||
}
|
||||
DataType.STR -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #${numElements-1}
|
||||
jsr prog8_lib.func_sort_ub""")
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
|
||||
in SplitWordArrayTypes -> TODO("split words sort")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcRor2(fcall: PtBuiltinFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
when (what.type) {
|
||||
@@ -827,8 +794,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")
|
||||
@@ -839,63 +805,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true, true)
|
||||
}
|
||||
|
||||
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
val dt = fcall.args.single().type
|
||||
val array = fcall.args[0] as PtIdentifier
|
||||
when (dt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
|
||||
outputAddressAndLengthOfArray(array)
|
||||
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
outputAddressAndLengthOfArray(array)
|
||||
asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
outputAddressAndLengthOfArray(array)
|
||||
asmgen.out(" jsr floats.func_${fcall.name}_f_into_A")
|
||||
}
|
||||
in SplitWordArrayTypes -> {
|
||||
val numElements = (asmgen.symbolTable.lookup(array.name) as StStaticVariable).length
|
||||
when(fcall.name) {
|
||||
"any" -> {
|
||||
// any(lsb-array) or any(msb-array)
|
||||
val arrayName = asmgen.asmVariableName(array)
|
||||
asmgen.out("""
|
||||
lda #<${arrayName}_lsb
|
||||
ldy #>${arrayName}_lsb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
""")
|
||||
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
|
||||
asmgen.out(" bne +") // shortcircuit
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda #<${arrayName}_msb
|
||||
ldy #>${arrayName}_msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
""")
|
||||
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
pla
|
||||
ora P8ZP_SCRATCH_REG
|
||||
+""")
|
||||
}
|
||||
"all" -> {
|
||||
TODO("split words all")
|
||||
}
|
||||
else -> throw AssemblyError("weird call")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes, true)
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
val dt = fcall.args.single().type
|
||||
@@ -1291,7 +1200,44 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
when(resultRegister) {
|
||||
if(arg is PtArrayIndexer && resultRegister in setOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
|
||||
// just read the msb byte out of the word array
|
||||
if(arg.splitWords) {
|
||||
val arrayVar = asmgen.asmVariableName(arg.variable)+"_msb"
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar,y")
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
|
||||
asmgen.out(" ldy $arrayVar,x")
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" ldx $arrayVar,y")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
val arrayVar = asmgen.asmVariableName(arg.variable)
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar+1,y")
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
|
||||
asmgen.out(" ldy $arrayVar+1,x")
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" ldx $arrayVar+1,y")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
}
|
||||
} else when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" tya")
|
||||
@@ -1306,6 +1252,23 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" tya | ldy #0 | cmp #0")
|
||||
}
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
|
||||
asmgen.out(" txa | ldx #0 | cmp #0")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
|
||||
asmgen.out(" tya | tax | ldy #0 | cpx #0")
|
||||
}
|
||||
in Cx16VirtualRegisters -> {
|
||||
val reg = "cx16.${resultRegister.toString().lowercase()}"
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" sty ${reg}L | lda #0 | sta ${reg}H | lda ${reg}L")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
}
|
||||
@@ -1347,7 +1310,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
|
||||
asmgen.out(" lda $arrayVar,x")
|
||||
asmgen.out(" ldy $arrayVar,x")
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
@@ -1385,11 +1348,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" ldy #0 | cpx #0")
|
||||
}
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.assignExpressionToRegister(arg, resultRegister)
|
||||
val zero = PtNumber(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
zero.parent=fcall
|
||||
assignAsmGen.assignExpressionToVariable(zero, "cx16.${resultRegister.toString().lowercase()}H", DataType.UBYTE)
|
||||
asmgen.out(" lda cx16.r0L")
|
||||
val reg = "cx16.${resultRegister.toString().lowercase()}"
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" sta ${reg}L | ldy #0 | sty ${reg}H | cmp #0")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
@@ -1398,8 +1359,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
|
||||
|
||||
@@ -37,7 +37,7 @@ internal class ForLoopsAsmGen(
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val stepsize=range.step.asConstInteger()!!
|
||||
|
||||
if(stepsize < -1) {
|
||||
@@ -48,25 +48,43 @@ internal class ForLoopsAsmGen(
|
||||
|
||||
when(iterableDt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
if (stepsize==1 || stepsize==-1) {
|
||||
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
|
||||
if (stepsize==-1 && range.to.asConstInteger()==0) {
|
||||
// simple loop downto 0 step -1
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
dec $varname
|
||||
lda $varname
|
||||
cmp #255
|
||||
bne $loopLabel""")
|
||||
}
|
||||
else if (stepsize==-1 && range.to.asConstInteger()==1) {
|
||||
// simple loop downto 1 step -1
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
dec $varname
|
||||
bne $loopLabel""")
|
||||
}
|
||||
else if (stepsize==1 || stepsize==-1) {
|
||||
// bytes array, step 1 or -1
|
||||
|
||||
val incdec = if(stepsize==1) "inc" else "dec"
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
|
||||
// pre-check for end already reached
|
||||
if(iterableDt==DataType.ARRAY_B) {
|
||||
asmgen.out(" sta $modifiedLabel+1")
|
||||
if(stepsize<0)
|
||||
if(stepsize<0) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
sbc $varname
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bpl $endLabel""")
|
||||
}
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
@@ -75,12 +93,13 @@ internal class ForLoopsAsmGen(
|
||||
eor #${'$'}80
|
||||
+ bmi $endLabel""")
|
||||
} else {
|
||||
if(stepsize<0)
|
||||
if(stepsize<0) {
|
||||
asmgen.out("""
|
||||
cmp $varname
|
||||
beq +
|
||||
bcs $endLabel
|
||||
+""")
|
||||
}
|
||||
else
|
||||
asmgen.out(" cmp $varname | bcc $endLabel")
|
||||
asmgen.out(" sta $modifiedLabel+1")
|
||||
@@ -100,8 +119,6 @@ $modifiedLabel cmp #0 ; modified
|
||||
// bytes, step >= 2 or <= -2
|
||||
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt))
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false)
|
||||
// pre-check for end already reached
|
||||
if(iterableDt==DataType.ARRAY_B) {
|
||||
@@ -155,58 +172,85 @@ $modifiedLabel cmp #0 ; modified
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
when {
|
||||
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvarWord(stmt, range)
|
||||
if(stepsize==-1 && range.to.asConstInteger()==0) {
|
||||
// simple loop downto 0 step -1 (words)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne ++
|
||||
lda $varname+1
|
||||
beq $endLabel
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
else if (stepsize==-1 && range.to.asConstInteger()==1) {
|
||||
// simple loop downto 1 step -1 (words)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #1
|
||||
bne +
|
||||
lda $varname+1
|
||||
beq $endLabel
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
else if (stepsize == 1 || stepsize == -1) {
|
||||
// words, step 1 or -1
|
||||
|
||||
stepsize == 1 || stepsize == -1 -> {
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvarWord(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname+1
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
bne +
|
||||
lda $varname
|
||||
$modifiedLabel2 cmp #0 ; modified
|
||||
beq $endLabel""")
|
||||
if(stepsize==1) {
|
||||
asmgen.out("""
|
||||
if(stepsize==1) {
|
||||
asmgen.out("""
|
||||
+ inc $varname
|
||||
bne $loopLabel
|
||||
inc $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
} else {
|
||||
asmgen.out("""
|
||||
asmgen.jmp(loopLabel)
|
||||
} else {
|
||||
asmgen.out("""
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
asmgen.jmp(loopLabel)
|
||||
}
|
||||
stepsize > 0 -> {
|
||||
|
||||
// (u)words, step >= 2
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvarWord(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
else if (stepsize > 0) {
|
||||
// (u)words, step >= 2
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.translate(stmt.statements)
|
||||
|
||||
if (iterableDt == DataType.ARRAY_UW) {
|
||||
asmgen.out("""
|
||||
if (iterableDt == DataType.ARRAY_UW) {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
clc
|
||||
adc #<$stepsize
|
||||
@@ -222,8 +266,8 @@ $modifiedLabel2 lda #0 ; modified
|
||||
bcc $endLabel
|
||||
bcs $loopLabel
|
||||
$endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
clc
|
||||
adc #<$stepsize
|
||||
@@ -239,22 +283,20 @@ $modifiedLabel lda #0 ; modified
|
||||
eor #$80
|
||||
+ bpl $loopLabel
|
||||
$endLabel""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
else {
|
||||
|
||||
// (u)words, step <= -2
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvarWord(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
// (u)words, step <= -2
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.translate(stmt.statements)
|
||||
|
||||
asmgen.out("""
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
sec
|
||||
sbc #<${stepsize.absoluteValue}
|
||||
@@ -271,13 +313,12 @@ $modifiedLabel sbc #0 ; modified
|
||||
eor #$80
|
||||
+ bpl $loopLabel
|
||||
$endLabel""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("range expression can only be byte or word")
|
||||
}
|
||||
|
||||
asmgen.loopEndLabels.pop()
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
|
||||
@@ -333,10 +374,9 @@ $endLabel""")
|
||||
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
asmgen.loopEndLabels.add(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
|
||||
@@ -478,17 +518,19 @@ $loopLabel sty $indexVar
|
||||
}
|
||||
else -> throw AssemblyError("can't iterate over $iterableDt")
|
||||
}
|
||||
asmgen.loopEndLabels.pop()
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
|
||||
if (range.isEmpty() || range.step==0)
|
||||
throw AssemblyError("empty range or step 0")
|
||||
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
|
||||
if(range.last==range.first) return translateForSimpleByteRangeAsc(stmt, range)
|
||||
if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range)
|
||||
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range)
|
||||
if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range, iterableDt==DataType.ARRAY_UB)
|
||||
}
|
||||
else if(iterableDt==DataType.ARRAY_W || iterableDt==DataType.ARRAY_UW) {
|
||||
if(range.last==range.first) return translateForSimpleWordRangeAsc(stmt, range)
|
||||
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
|
||||
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
|
||||
}
|
||||
@@ -496,7 +538,7 @@ $loopLabel sty $indexVar
|
||||
// not one of the easy cases, generate more complex code...
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
when(iterableDt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
// loop over byte range via loopvar, step >= 2 or <= -2
|
||||
@@ -508,7 +550,7 @@ $loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
when (range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $range ${stmt.position}")
|
||||
}
|
||||
2 -> {
|
||||
if(range.last==255 || range.last==254) {
|
||||
@@ -602,13 +644,13 @@ $loopLabel""")
|
||||
}
|
||||
else -> throw AssemblyError("range expression can only be byte or word")
|
||||
}
|
||||
asmgen.loopEndLabels.pop()
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #${range.first}
|
||||
@@ -628,50 +670,50 @@ $endLabel""")
|
||||
bne $loopLabel
|
||||
$endLabel""")
|
||||
}
|
||||
asmgen.loopEndLabels.pop()
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression, unsigned: Boolean) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #${range.first}
|
||||
sta $varname
|
||||
lda #${range.first}
|
||||
sta $varname
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
when (range.last) {
|
||||
0 -> {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
beq $endLabel
|
||||
dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
if(!unsigned || range.first<=127) {
|
||||
asmgen.out("""
|
||||
dec $varname
|
||||
bpl $loopLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
dec $varname
|
||||
lda $varname
|
||||
cmp #255
|
||||
bne $loopLabel""")
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
asmgen.out("""
|
||||
dec $varname
|
||||
bne $loopLabel
|
||||
$endLabel""")
|
||||
bne $loopLabel""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out("""
|
||||
dec $varname
|
||||
lda $varname
|
||||
cmp #${range.last-1}
|
||||
bne $loopLabel
|
||||
$endLabel""")
|
||||
bne $loopLabel""")
|
||||
}
|
||||
}
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #<${range.first}
|
||||
@@ -692,13 +734,13 @@ $loopLabel""")
|
||||
inc $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.pop()
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #<${range.first}
|
||||
@@ -707,20 +749,29 @@ $loopLabel""")
|
||||
sty $varname+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
if(range.last==0) {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne ++
|
||||
lda $varname+1
|
||||
beq $endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<${range.last}
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
beq $endLabel""")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<${range.last}
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
beq $endLabel
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.pop()
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
|
||||
|
||||
@@ -81,8 +81,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
is PtAddressOf -> false
|
||||
is PtIdentifier -> false
|
||||
is PtMachineRegister -> false
|
||||
is PtMemoryByte -> return usesOtherRegistersWhileEvaluating(arg.address)
|
||||
is PtIrRegister -> false
|
||||
is PtMemoryByte -> arg.address !is PtNumber && arg.address !is PtIdentifier
|
||||
is PtNumber -> false
|
||||
is PtBool -> false
|
||||
else -> true
|
||||
@@ -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}
|
||||
@@ -110,9 +110,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
|
||||
}
|
||||
else {
|
||||
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
|
||||
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, usedA())
|
||||
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, usedA())
|
||||
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, usedA())
|
||||
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
@@ -205,7 +205,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(value is PtIdentifier) {
|
||||
val addr = PtAddressOf(Position.DUMMY)
|
||||
addr.add(value)
|
||||
addr.parent = sub as PtNode
|
||||
addr.parent = scope as PtNode
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
@@ -213,7 +213,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
}
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, target.position), scope)
|
||||
}
|
||||
return RegisterOrStatusflag(register, null)
|
||||
}
|
||||
|
||||
@@ -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 */ }
|
||||
@@ -83,9 +83,80 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun fallbackTranslateForSimpleCondition(ifElse: PtIfElse) {
|
||||
val bittest = ifElse.condition as? PtBuiltinFunctionCall
|
||||
val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump
|
||||
|
||||
if(bittest!=null && bittest.name.startsWith("prog8_ifelse_bittest_")) {
|
||||
val variable = bittest.args[0] as PtIdentifier
|
||||
val bitnumber = (bittest.args[1] as PtNumber).number.toInt()
|
||||
val testForBitSet = bittest.name.endsWith("_set")
|
||||
when (bitnumber) {
|
||||
7 -> {
|
||||
// test via bit + N flag
|
||||
asmgen.out(" bit ${variable.name}")
|
||||
if(testForBitSet) {
|
||||
if(jumpAfterIf!=null) {
|
||||
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
|
||||
if(indirect)
|
||||
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
|
||||
if(ifElse.hasElse())
|
||||
throw AssemblyError("didn't expect else part here ${ifElse.position}")
|
||||
else
|
||||
asmgen.out(" bmi $asmLabel")
|
||||
}
|
||||
else
|
||||
translateIfElseBodies("bpl", ifElse)
|
||||
} else {
|
||||
if(jumpAfterIf!=null) {
|
||||
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
|
||||
if(indirect)
|
||||
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
|
||||
if(ifElse.hasElse())
|
||||
throw AssemblyError("didn't expect else part here ${ifElse.position}")
|
||||
else
|
||||
asmgen.out(" bpl $asmLabel")
|
||||
}
|
||||
else
|
||||
translateIfElseBodies("bmi", ifElse)
|
||||
}
|
||||
return
|
||||
}
|
||||
6 -> {
|
||||
// test via bit + V flag
|
||||
asmgen.out(" bit ${variable.name}")
|
||||
if(testForBitSet) {
|
||||
if(jumpAfterIf!=null) {
|
||||
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
|
||||
if(indirect)
|
||||
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
|
||||
if(ifElse.hasElse())
|
||||
throw AssemblyError("didn't expect else part here ${ifElse.position}")
|
||||
else
|
||||
asmgen.out(" bvs $asmLabel")
|
||||
}
|
||||
else
|
||||
translateIfElseBodies("bvc", ifElse)
|
||||
} else {
|
||||
if(jumpAfterIf!=null) {
|
||||
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
|
||||
if(indirect)
|
||||
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
|
||||
if(ifElse.hasElse())
|
||||
throw AssemblyError("didn't expect else part here ${ifElse.position}")
|
||||
else
|
||||
asmgen.out(" bvc $asmLabel")
|
||||
}
|
||||
else
|
||||
translateIfElseBodies("bvs", ifElse)
|
||||
}
|
||||
return
|
||||
}
|
||||
else -> throw AssemblyError("prog8_ifelse_bittest can only work on bits 7 and 6")
|
||||
}
|
||||
}
|
||||
|
||||
// the condition is "simple" enough to just assign its 0/1 value to a register and branch on that
|
||||
assignConditionValueToRegisterAndTest(ifElse.condition)
|
||||
val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump
|
||||
if(jumpAfterIf!=null)
|
||||
translateJumpElseBodies("bne", "beq", jumpAfterIf, ifElse.elseScope)
|
||||
else
|
||||
@@ -193,7 +264,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 +476,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 +687,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 +808,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 +919,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 +1056,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 +1585,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 +1633,7 @@ _jump jmp ($asmLabel)
|
||||
else -> {
|
||||
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
|
||||
val varname = asmgen.asmVariableName(left)
|
||||
translateAYEquals(varname, varname+"+1")
|
||||
translateAYEquals(varname, "$varname+1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,12 @@ internal class ProgramAndVarsGen(
|
||||
OutputType.RAW -> {
|
||||
asmgen.out("; ---- raw assembler program ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
asmgen.out(" cld")
|
||||
asmgen.out(" tsx ; save stackpointer for sys.exit()")
|
||||
asmgen.out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
}
|
||||
OutputType.PRG -> {
|
||||
when(options.launcher) {
|
||||
@@ -103,13 +109,20 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
|
||||
asmgen.out("+\t.word 0")
|
||||
asmgen.out("prog8_entrypoint\t; assembly code starts here")
|
||||
asmgen.out(" cld")
|
||||
asmgen.out(" tsx ; save stackpointer for sys.exit()")
|
||||
asmgen.out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
}
|
||||
CbmPrgLauncherType.NONE -> {
|
||||
// this is the same as RAW
|
||||
asmgen.out("; ---- program without basic sys call ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
asmgen.out(" cld")
|
||||
asmgen.out(" tsx ; save stackpointer for sys.exit()")
|
||||
asmgen.out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
@@ -119,6 +132,9 @@ internal class ProgramAndVarsGen(
|
||||
OutputType.XEX -> {
|
||||
asmgen.out("; ---- atari xex program ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
asmgen.out(" cld")
|
||||
asmgen.out(" tsx ; save stackpointer for sys.exit()")
|
||||
asmgen.out(" stx prog8_lib.orig_stackpointer")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr sys.init_system")
|
||||
asmgen.out(" jsr sys.init_system_phase2")
|
||||
@@ -149,7 +165,10 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" jsr p8b_main.p8s_start | lda #0 | sta ${"$"}ff00")
|
||||
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||
}
|
||||
else -> asmgen.jmp("p8b_main.p8s_start")
|
||||
else -> {
|
||||
asmgen.out(" jsr p8b_main.p8s_start")
|
||||
asmgen.out(" jmp sys.cleanup_at_exit")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,22 +470,10 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun entrypointInitialization() {
|
||||
asmgen.out("; program startup initialization")
|
||||
asmgen.out(" cld | tsx | stx prog8_lib.orig_stackpointer ; required for sys.exit()")
|
||||
// set full BSS area to zero
|
||||
asmgen.out("""
|
||||
.if prog8_bss_section_size>0
|
||||
; reset all variables in BSS section to zero
|
||||
lda #<prog8_bss_section_start
|
||||
ldy #>prog8_bss_section_start
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx #<prog8_bss_section_size
|
||||
ldy #>prog8_bss_section_size
|
||||
lda #0
|
||||
jsr prog8_lib.memset
|
||||
.endif""")
|
||||
// zero out the BSS area first, before setting the variable init values
|
||||
asmgen.out(" jsr prog8_lib.program_startup_clear_bss")
|
||||
|
||||
// initialize block-level (global) variables at program start
|
||||
blockVariableInitializers.forEach {
|
||||
if (it.value.isNotEmpty())
|
||||
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
|
||||
@@ -517,9 +524,7 @@ internal class ProgramAndVarsGen(
|
||||
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
|
||||
}
|
||||
|
||||
asmgen.out("""+
|
||||
clv
|
||||
clc""")
|
||||
asmgen.out("+")
|
||||
}
|
||||
|
||||
private class ZpStringWithInitial(
|
||||
@@ -579,7 +584,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("")
|
||||
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
|
||||
if(varsNoInit.isNotEmpty()) {
|
||||
asmgen.out("; non-zeropage variables without initialization value")
|
||||
asmgen.out("; non-zeropage variables")
|
||||
asmgen.out(" .section BSS")
|
||||
varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
|
||||
uninitializedVariable2asm(it)
|
||||
@@ -588,7 +593,7 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
if(varsWithInit.isNotEmpty()) {
|
||||
asmgen.out("; non-zeropage variables")
|
||||
asmgen.out("; non-zeropage variables with init value")
|
||||
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
|
||||
stringvars.forEach {
|
||||
outputStringvar(
|
||||
@@ -635,8 +640,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")
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -221,17 +175,14 @@ internal class AnyExprAsmGen(
|
||||
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
|
||||
when(asmgen.options.compTarget.name) {
|
||||
C64Target.NAME -> {
|
||||
// c64 has a quirk: always make sure FAC2 is loaded last (done using CONUPK) otherwise the result will be corrupt on C64
|
||||
// this requires some more forced copying around of float values in certain cases
|
||||
if (right.isSimple()) {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
|
||||
asmgen.pushFAC1()
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||
asmgen.popFAC2()
|
||||
}
|
||||
// C64 math library has a quirk: you have always make sure FAC2/ARG is loaded last (done using CONUPK)
|
||||
// otherwise the result of certain floating point operations such as FDIVT will be wrong.
|
||||
// see https://www.c64-wiki.com/wiki/CONUPK
|
||||
// Unfortunately this means we have to push and pop an intermediary floating point value to and from memory.
|
||||
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true)
|
||||
asmgen.pushFAC1()
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||
asmgen.popFAC2()
|
||||
}
|
||||
Cx16Target.NAME -> {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
|
||||
|
||||
@@ -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.*
|
||||
@@ -9,11 +11,12 @@ import prog8.codegen.cpu6502.VariableAllocator
|
||||
import prog8.codegen.cpu6502.returnsWhatWhere
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val options: CompilationOptions,
|
||||
private val anyExprGen: AnyExprAsmGen,
|
||||
private val allocator: VariableAllocator) {
|
||||
internal class AssignmentAsmGen(
|
||||
private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val anyExprGen: AnyExprAsmGen,
|
||||
private val allocator: VariableAllocator
|
||||
) {
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator)
|
||||
|
||||
fun translate(assignment: PtAssignment) {
|
||||
@@ -32,6 +35,164 @@ 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)
|
||||
|
||||
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.isEmpty())
|
||||
assignRegisterResults(registersResults)
|
||||
else if(registersResults.isEmpty())
|
||||
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
|
||||
else
|
||||
assignStatusFlagsAndRegistersResults(statusFlagResults, registersResults)
|
||||
} else {
|
||||
throw AssemblyError("number of values and targets don't match")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignStatusFlagsAndRegistersResults(
|
||||
statusFlagResults: List<Pair<StRomSubParameter, PtNode>>,
|
||||
registersResults: List<Pair<StRomSubParameter, PtNode>>
|
||||
) {
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
|
||||
} else {
|
||||
val saveA = needsToSaveA(registersResults)
|
||||
assignOnlyTheStatusFlagsResults(saveA, statusFlagResults)
|
||||
assignRegisterResults(registersResults)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignOnlyTheStatusFlagsResults(saveA: Boolean, statusFlagResults: List<Pair<StRomSubParameter, PtNode>>) {
|
||||
// assigning flags to their variables targets requires load-instructions that destroy flags
|
||||
// so if there's more than 1, we need to save and restore the flags
|
||||
val saveFlags = statusFlagResults.size>1
|
||||
|
||||
fun hasFlag(statusFlagResults: List<Pair<StRomSubParameter, PtNode>>, flag: Statusflag): PtAssignTarget? {
|
||||
for ((returns, target) in statusFlagResults) {
|
||||
if(returns.register.statusflag!! == flag)
|
||||
return target as PtAssignTarget
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
val targetCarry = hasFlag(statusFlagResults, Statusflag.Pc)
|
||||
val targetZero = hasFlag(statusFlagResults, Statusflag.Pz)
|
||||
val targetNeg = hasFlag(statusFlagResults, Statusflag.Pn)
|
||||
val targetOverflow = hasFlag(statusFlagResults, Statusflag.Pv)
|
||||
|
||||
if(saveA) asmgen.out(" pha")
|
||||
if(targetZero!=null && !targetZero.void)
|
||||
assignZeroFlagResult(targetZero, saveFlags)
|
||||
if(targetNeg!=null && !targetNeg.void)
|
||||
assignNegativeFlagResult(targetNeg, saveFlags)
|
||||
if(targetCarry!=null && !targetCarry.void)
|
||||
assignCarryFlagResult(targetCarry)
|
||||
if(targetOverflow!=null && !targetOverflow.void)
|
||||
assignOverflowFlagResult(targetOverflow)
|
||||
if(saveA) asmgen.out(" pla")
|
||||
}
|
||||
|
||||
private 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignCarryFlagResult(target: PtAssignTarget) {
|
||||
// overflow is not clobbered so no need to save/restore it
|
||||
asmgen.out(" lda #0 | rol a")
|
||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||
assignRegisterByte(tgt, CpuRegister.A, false, false)
|
||||
}
|
||||
|
||||
private fun assignZeroFlagResult(target: PtAssignTarget, saveFlags: Boolean) {
|
||||
if(saveFlags) asmgen.out(" php")
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||
assignRegisterByte(tgt, CpuRegister.A, false, false)
|
||||
if(saveFlags) asmgen.out(" plp")
|
||||
}
|
||||
|
||||
private fun assignNegativeFlagResult(target: PtAssignTarget, saveFlags: Boolean) {
|
||||
if(saveFlags) asmgen.out(" php")
|
||||
asmgen.out("""
|
||||
bmi +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||
assignRegisterByte(tgt, CpuRegister.A, false, false)
|
||||
if(saveFlags) asmgen.out(" plp")
|
||||
}
|
||||
|
||||
private fun assignOverflowFlagResult(target: PtAssignTarget) {
|
||||
// overflow is not clobbered so no need to save/restore it
|
||||
asmgen.out("""
|
||||
bvs +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||
assignRegisterByte(tgt, CpuRegister.A, false, false)
|
||||
}
|
||||
|
||||
fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) {
|
||||
when(assign.source.kind) {
|
||||
SourceStorageKind.LITERALBOOLEAN -> {
|
||||
@@ -60,7 +221,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
val variable = assign.source.asmVarname
|
||||
when (assign.target.datatype) {
|
||||
DataType.BOOL -> {
|
||||
if (assign.source.datatype == DataType.BOOL || !options.strictBool) assignVariableByte(assign.target, variable)
|
||||
if (assign.source.datatype == DataType.BOOL) assignVariableByte(assign.target, variable)
|
||||
else throw AssemblyError("assigning non-bool variable to boolean, should have been typecasted")
|
||||
}
|
||||
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
|
||||
@@ -143,12 +304,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
SourceStorageKind.MEMORY -> {
|
||||
fun assignViaExprEval(expression: PtExpression) {
|
||||
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, true)
|
||||
}
|
||||
|
||||
val value = assign.source.memory!!
|
||||
when (value.address) {
|
||||
is PtNumber -> {
|
||||
@@ -163,10 +318,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, false)) {
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, true)
|
||||
} else {
|
||||
assignViaExprEval(value.address)
|
||||
assignByteFromAddressExpression(value.address, assign.target)
|
||||
}
|
||||
}
|
||||
else -> assignViaExprEval(value.address)
|
||||
else -> assignByteFromAddressExpression(value.address, assign.target)
|
||||
}
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
@@ -178,6 +333,106 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignByteFromAddressExpression(address: PtExpression, target: AsmAssignTarget) {
|
||||
if(address is PtBinaryExpression) {
|
||||
if(address.operator=="+" && address.right.type==DataType.UWORD) {
|
||||
if (address.left is PtIdentifier) {
|
||||
// use (zp),Y instead of explicitly calculating the full zp pointer value
|
||||
val pointer = (address.left as PtIdentifier).name
|
||||
when(val index=address.right) {
|
||||
is PtIdentifier -> {
|
||||
val indexName = index.name
|
||||
asmgen.out("""
|
||||
lda $pointer
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda $pointer+1
|
||||
clc
|
||||
adc $indexName+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldy $indexName
|
||||
lda (P8ZP_SCRATCH_W2),y""")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
}
|
||||
is PtNumber -> {
|
||||
val indexValue = index.number.toInt().toString()
|
||||
asmgen.out("""
|
||||
lda $pointer
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda $pointer+1
|
||||
clc
|
||||
adc #>$indexValue
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldy #<$indexValue
|
||||
lda (P8ZP_SCRATCH_W2),y""")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// else if(address.operator=="-") {
|
||||
// // does this ever occur? we could optimize it too, but it seems like a pathological case
|
||||
// }
|
||||
}
|
||||
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
}
|
||||
|
||||
private fun storeByteInAToAddressExpression(address: PtExpression, saveA: Boolean) {
|
||||
if(address is PtBinaryExpression) {
|
||||
if(address.operator=="+") {
|
||||
if (address.left is PtIdentifier && address.right.type==DataType.UWORD) {
|
||||
// use (zp),Y instead of explicitly calculating the full zp pointer value
|
||||
val pointer = (address.left as PtIdentifier).name
|
||||
when(val index=address.right) {
|
||||
is PtIdentifier -> {
|
||||
val indexName = index.name
|
||||
asmgen.out("""
|
||||
tax
|
||||
lda $pointer
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda $pointer+1
|
||||
clc
|
||||
adc $indexName+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldy $indexName
|
||||
txa
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
return
|
||||
}
|
||||
is PtNumber -> {
|
||||
val indexValue = index.number.toInt().toString()
|
||||
asmgen.out("""
|
||||
tax
|
||||
lda $pointer
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda $pointer+1
|
||||
clc
|
||||
adc #>$indexValue
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldy #<$indexValue
|
||||
txa
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// else if(address.operator=="-") {
|
||||
// // does this ever occur? we could optimize it too, but it seems like a pathological case
|
||||
// }
|
||||
}
|
||||
if(saveA) asmgen.out(" pha")
|
||||
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
if(saveA) asmgen.out(" pla")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
}
|
||||
|
||||
|
||||
private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) {
|
||||
when(val value = assign.source.expression!!) {
|
||||
is PtAddressOf -> {
|
||||
@@ -562,8 +817,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
RegisterOrPair.Y -> "y"
|
||||
else -> TODO("comparison to word register")
|
||||
}
|
||||
val assignTrue = PtInlineAssembly(" ld${reg} #1", false, assign.target.position)
|
||||
val assignFalse = PtInlineAssembly(" ld${reg} #0", false, assign.target.position)
|
||||
val assignTrue = PtInlineAssembly("\tld${reg} #1", false, assign.target.position)
|
||||
val assignFalse = PtInlineAssembly("\tld${reg} #0", false, assign.target.position)
|
||||
ifPart.add(assignTrue)
|
||||
elsePart.add(assignFalse)
|
||||
val ifelse = PtIfElse(assign.position)
|
||||
@@ -597,7 +852,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) {
|
||||
@@ -614,11 +868,11 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
sta ${assign.target.asmVarname}+1""", false, assign.target.position)
|
||||
}
|
||||
} else {
|
||||
assignTrue = PtInlineAssembly(" lda #1\n sta ${assign.target.asmVarname}", false, assign.target.position)
|
||||
assignTrue = PtInlineAssembly("\tlda #1\n sta ${assign.target.asmVarname}", false, assign.target.position)
|
||||
}
|
||||
}
|
||||
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 +882,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 +921,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
|
||||
@@ -699,6 +953,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun optimizedDivideExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
|
||||
// replacing division by shifting is done in an optimizer step.
|
||||
when(expr.type) {
|
||||
DataType.UBYTE -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
@@ -870,7 +1125,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
} else {
|
||||
if (signed && shifts > 0) {
|
||||
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
|
||||
if(shifts==1)
|
||||
asmgen.out(" cmp #$80 | ror a")
|
||||
else
|
||||
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
|
||||
} else {
|
||||
repeat(shifts) {
|
||||
asmgen.out(" lsr a")
|
||||
@@ -904,7 +1162,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
} else {
|
||||
if(signed && shifts>0) {
|
||||
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
|
||||
if(shifts==1) {
|
||||
asmgen.out("""
|
||||
pha
|
||||
tya
|
||||
cmp #$80
|
||||
ror a
|
||||
tay
|
||||
pla
|
||||
ror a""")
|
||||
}
|
||||
else
|
||||
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
|
||||
} else {
|
||||
if(shifts>0) {
|
||||
asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||
@@ -968,10 +1237,17 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
is PtNumber -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out(" clc | adc #${right.number.toHex()}")
|
||||
else
|
||||
asmgen.out(" sec | sbc #${right.number.toHex()}")
|
||||
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if (expr.operator == "+")
|
||||
asmgen.out(" inc a")
|
||||
else
|
||||
asmgen.out(" dec a")
|
||||
} else {
|
||||
if (expr.operator == "+")
|
||||
asmgen.out(" clc | adc #${right.number.toHex()}")
|
||||
else
|
||||
asmgen.out(" sec | sbc #${right.number.toHex()}")
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
|
||||
return true
|
||||
}
|
||||
@@ -1106,8 +1382,39 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
is PtNumber -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
inc a
|
||||
bne +
|
||||
iny
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
dec a
|
||||
cmp #255
|
||||
bne +
|
||||
dey
|
||||
+""")
|
||||
}
|
||||
} else if(dt!=DataType.WORD && right.number.toInt() in 0..255) {
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #${right.number.toHex()}
|
||||
bcc +
|
||||
iny
|
||||
+""") } else if(expr.operator=="-") {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #${right.number.toHex()}
|
||||
bcs +
|
||||
dey
|
||||
+""")
|
||||
}
|
||||
} else {
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<${right.number.toHex()}
|
||||
tax
|
||||
@@ -1115,8 +1422,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
adc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
} else if(expr.operator=="-") {
|
||||
asmgen.out("""
|
||||
} else if(expr.operator=="-") {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<${right.number.toHex()}
|
||||
tax
|
||||
@@ -1124,6 +1431,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
sbc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
}
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
@@ -1195,11 +1503,16 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
// special optimization for bytevalue +/- pointervar[y] (actually: bytevalue +/- @(address) )
|
||||
val address = mem.address as? PtBinaryExpression
|
||||
if(address is PtBinaryExpression) {
|
||||
val constOffset = address.right.asConstInteger()
|
||||
// check that the offset is actually a byte (so that it fits in a single register)
|
||||
if(constOffset==null && address.right.type !in ByteDatatypes)
|
||||
return false
|
||||
if(constOffset!=null && constOffset !in -128..255)
|
||||
return false
|
||||
val ptrVar = address.left as? PtIdentifier
|
||||
if(ptrVar!=null && asmgen.isZpVar(ptrVar)) {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, false)
|
||||
val pointername = asmgen.asmVariableName(ptrVar)
|
||||
val constOffset = address.right.asConstInteger()
|
||||
if (constOffset != null && constOffset < 256) {
|
||||
// we have value + @(zpptr + 255), or value - @(zpptr+255)
|
||||
asmgen.out(" ldy #$constOffset")
|
||||
@@ -1454,6 +1767,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""")
|
||||
@@ -1516,9 +1830,56 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun containmentCheckIntoA(containment: PtContainmentCheck) {
|
||||
val elementDt = containment.element.type
|
||||
val symbol = asmgen.symbolTable.lookup(containment.iterable.name)!!
|
||||
val symbolName = asmgen.asmVariableName(symbol, containment.definingSub())
|
||||
val elementDt = containment.needle.type
|
||||
|
||||
if(containment.haystackValues!=null) {
|
||||
val haystack = containment.haystackValues!!.children.map {
|
||||
if(it is PtBool) it.asInt()
|
||||
else (it as PtNumber).number.toInt()
|
||||
}
|
||||
when(elementDt) {
|
||||
in ByteDatatypesWithBoolean -> {
|
||||
require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
|
||||
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
|
||||
for(number in haystack) {
|
||||
asmgen.out("""
|
||||
cmp #$number
|
||||
beq +""")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
|
||||
assignExpressionToRegister(containment.needle, RegisterOrPair.AY, elementDt == DataType.WORD)
|
||||
val gottemLabel = asmgen.makeLabel("gottem")
|
||||
val endLabel = asmgen.makeLabel("end")
|
||||
for(number in haystack) {
|
||||
asmgen.out("""
|
||||
cmp #<$number
|
||||
bne +
|
||||
cpy #>$number
|
||||
beq $gottemLabel
|
||||
+ """)
|
||||
}
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
beq $endLabel
|
||||
$gottemLabel lda #1
|
||||
$endLabel""")
|
||||
}
|
||||
DataType.FLOAT -> throw AssemblyError("containmentchecks for floats should always be done on an array variable with subroutine")
|
||||
else -> throw AssemblyError("weird dt $elementDt")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
val symbol = asmgen.symbolTable.lookup(containment.haystackHeapVar!!.name)!!
|
||||
val symbolName = asmgen.asmVariableName(symbol, containment.definingISub())
|
||||
val (dt, numElements) = when(symbol) {
|
||||
is StStaticVariable -> symbol.dt to symbol.length!!
|
||||
is StMemVar -> symbol.dt to symbol.length!!
|
||||
@@ -1526,7 +1887,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
when(dt) {
|
||||
DataType.STR -> {
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
|
||||
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
|
||||
asmgen.out(" pha") // need to keep the scratch var safe so we have to do it in this order
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position,"P8ZP_SCRATCH_W1"), symbolName, null, null)
|
||||
asmgen.out(" pla")
|
||||
@@ -1534,13 +1895,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.FAC1, true)
|
||||
assignExpressionToRegister(containment.needle, RegisterOrPair.FAC1, true)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W1"), symbolName, null, null)
|
||||
asmgen.out(" ldy #$numElements")
|
||||
asmgen.out(" jsr floats.containment_floatarray")
|
||||
}
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt == DataType.BYTE)
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_BOOL -> {
|
||||
assignExpressionToRegister(containment.needle, RegisterOrPair.A, elementDt == DataType.BYTE)
|
||||
asmgen.out(" pha") // need to keep the scratch var safe so we have to do it in this order
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W1"), symbolName, null, null)
|
||||
asmgen.out(" pla")
|
||||
@@ -1548,7 +1909,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt)
|
||||
assignExpressionToVariable(containment.needle, "P8ZP_SCRATCH_W1", elementDt)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_W2"), symbolName, null, null)
|
||||
asmgen.out(" ldy #$numElements")
|
||||
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
||||
@@ -1662,8 +2023,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 +2033,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 +2543,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #$constIndex
|
||||
bne +
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
}
|
||||
@@ -2207,7 +2568,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
bne +
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
}
|
||||
@@ -2217,7 +2578,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
ldy #>$sourceName
|
||||
clc
|
||||
adc #<$sourceName
|
||||
bne +
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
}
|
||||
@@ -3002,11 +3363,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
else {
|
||||
if (regs !in Cx16VirtualRegisters) {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | phx")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | phy")
|
||||
RegisterOrPair.XY -> asmgen.out(" phx | phy")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
} else {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
}
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
@@ -3045,11 +3415,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
else {
|
||||
if (regs !in Cx16VirtualRegisters) {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | phx")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | phy")
|
||||
RegisterOrPair.XY -> asmgen.out(" phx | phy")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
} else {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
}
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
@@ -3548,17 +3927,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
|
||||
fun storeViaExprEval() {
|
||||
when(addressExpr) {
|
||||
is PtNumber, is PtIdentifier -> {
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
}
|
||||
else -> {
|
||||
// same as above but we need to save the A register
|
||||
asmgen.out(" pha")
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.out(" pla")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
}
|
||||
is PtNumber, is PtIdentifier -> storeByteInAToAddressExpression(addressExpr, false)
|
||||
else -> storeByteInAToAddressExpression(addressExpr, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+57
-29
@@ -225,7 +225,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationSplitWordWithLiteralval(target.asmVarname, target.datatype, index, operator, value.number!!.number.toInt())
|
||||
else -> {
|
||||
// TODO: more optimized code for VARIABLE, REGISTER, MEMORY, ARRAY, EXPRESSION in the case of split-word arrays
|
||||
val scope = target.origAstTarget?.definingSub()
|
||||
val scope = target.origAstTarget?.definingISub()
|
||||
val regTarget = AsmAssignTarget.fromRegisters(RegisterOrPair.R0, false, target.position, scope, asmgen)
|
||||
val assignToReg = AsmAssignment(value, regTarget, program.memsizer, target.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assignToReg, scope)
|
||||
@@ -1086,6 +1086,21 @@ $shortcutLabel:""")
|
||||
}
|
||||
}
|
||||
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if(operator=="&" && value is PtPrefix && value.operator=="~") {
|
||||
// M &= ~A --> use TRB 65c02 instruction for that
|
||||
asmgen.assignExpressionToRegister(value.value, RegisterOrPair.A, dt in SignedDatatypes)
|
||||
asmgen.out(" trb $name")
|
||||
return
|
||||
}
|
||||
else if(operator=="|") {
|
||||
// M |= A --> use TSB 65c02 instruction for that
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
|
||||
asmgen.out(" tsb $name")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// normal evaluation
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
|
||||
inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt in SignedDatatypes)
|
||||
@@ -1094,6 +1109,15 @@ $shortcutLabel:""")
|
||||
|
||||
private fun inplacemodificationByteVariableWithVariable(name: String, dt: DataType, operator: String, otherName: String) {
|
||||
// note: no logical and/or shortcut here, not worth it due to simple right operand
|
||||
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if(operator=="|") {
|
||||
// M |= A --> use TSB 65c02 instruction for that
|
||||
asmgen.out(" lda $otherName | tsb $name")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
asmgen.out(" lda $name")
|
||||
inplacemodificationRegisterAwithVariable(operator, otherName, dt in SignedDatatypes)
|
||||
asmgen.out(" sta $name")
|
||||
@@ -1459,6 +1483,7 @@ $shortcutLabel:""")
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name")
|
||||
}
|
||||
"/" -> {
|
||||
// replacing division by shifting is done in an optimizer step.
|
||||
if (dt == DataType.UBYTE)
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name")
|
||||
else
|
||||
@@ -1642,7 +1667,7 @@ $shortcutLabel:""")
|
||||
}
|
||||
|
||||
private fun immediateOrInplace(name: String, value: Int) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02) && ((value and (value-1))==0)) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
asmgen.out(" lda #$value | tsb $name") // set bit
|
||||
} else {
|
||||
asmgen.out(" lda $name | ora #$value | sta $name")
|
||||
@@ -1703,7 +1728,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?) {
|
||||
@@ -1807,33 +1832,36 @@ $shortcutLabel:""")
|
||||
}
|
||||
}
|
||||
"/" -> {
|
||||
if(value==0)
|
||||
// replacing division by shifting is done in an optimizer step.
|
||||
if(value==0) {
|
||||
throw AssemblyError("division by zero")
|
||||
if(dt==DataType.WORD) {
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
ldy $msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_w_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
}
|
||||
else {
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
ldy $msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_uw_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
} else {
|
||||
if(dt==DataType.WORD) {
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
ldy $msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_w_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
}
|
||||
else {
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
ldy $msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_uw_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
"%" -> {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,8 @@ plugins {
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
targetCompatibility = JavaLanguageVersion.of(javaVersion)
|
||||
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
@@ -28,10 +27,11 @@ 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.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
|
||||
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -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,70 @@ 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 -> {
|
||||
TODO("assign CPU status flag ${returns.register.statusflag!!}")
|
||||
}
|
||||
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 +84,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 +107,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 +136,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 +325,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 +499,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 +1387,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 +1410,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?
|
||||
|
||||
@@ -9,8 +9,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
return when(call.name) {
|
||||
"any" -> funcAny(call)
|
||||
"all" -> funcAll(call)
|
||||
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
|
||||
"cmp" -> funcCmp(call)
|
||||
"sgn" -> funcSgn(call)
|
||||
@@ -19,6 +17,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"divmod__uword" -> funcDivmod(call, IRDataType.WORD)
|
||||
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
|
||||
"callfar" -> funcCallfar(call)
|
||||
"callfar2" -> funcCallfar2(call)
|
||||
"call" -> funcCall(call)
|
||||
"msb" -> funcMsb(call)
|
||||
"lsb" -> funcLsb(call)
|
||||
@@ -34,14 +33,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
|
||||
"sort" -> funcSort(call)
|
||||
"reverse" -> funcReverse(call)
|
||||
"setlsb" -> funcSetLsbMsb(call, false)
|
||||
"setmsb" -> funcSetLsbMsb(call, true)
|
||||
"rol" -> funcRolRor(call)
|
||||
"ror" -> funcRolRor(call)
|
||||
"rol2" -> funcRolRor(call)
|
||||
"ror2" -> funcRolRor(call)
|
||||
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
|
||||
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(call)
|
||||
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
|
||||
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
|
||||
@@ -132,10 +131,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 {
|
||||
@@ -151,6 +150,29 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcCallfar2(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val bankTr = exprGen.translateExpression(call.args[0])
|
||||
val addressTr = exprGen.translateExpression(call.args[1])
|
||||
val argumentA = exprGen.translateExpression(call.args[2])
|
||||
val argumentX = exprGen.translateExpression(call.args[3])
|
||||
val argumentY = exprGen.translateExpression(call.args[4])
|
||||
val argumentCarry = exprGen.translateExpression(call.args[5])
|
||||
addToResult(result, bankTr, bankTr.resultReg, -1)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
addToResult(result, argumentA, argumentA.resultReg, -1)
|
||||
addToResult(result, argumentX, argumentX.resultReg, -1)
|
||||
addToResult(result, argumentY, argumentY.resultReg, -1)
|
||||
addToResult(result, argumentCarry, argumentCarry.resultReg, -1)
|
||||
result += codeGen.makeSyscall(IMSyscall.CALLFAR2, listOf(IRDataType.BYTE to bankTr.resultReg, IRDataType.WORD to addressTr.resultReg,
|
||||
IRDataType.BYTE to argumentA.resultReg,
|
||||
IRDataType.BYTE to argumentX.resultReg,
|
||||
IRDataType.BYTE to argumentY.resultReg,
|
||||
IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to addressTr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, addressTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcDivmod(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val number = call.args[0]
|
||||
@@ -204,79 +226,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcAny(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
|
||||
if(arrayName.type in SplitWordArrayTypes) {
|
||||
// any(lsb-array) or any(msb-array)
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
|
||||
addToResult(result, trLsb, trLsb.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trLsb.resultReg)
|
||||
val shortcircuitLabel = codeGen.createLabelName()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = trLsb.resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcircuitLabel)
|
||||
it += IRInstruction(Opcode.PREPARECALL, immediate = 2)
|
||||
}
|
||||
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
|
||||
addToResult(result, trMsb, trMsb.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trMsb.resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=trLsb.resultReg, reg2=trMsb.resultReg), null)
|
||||
result += IRCodeChunk(shortcircuitLabel, null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, trLsb.resultReg, -1)
|
||||
}
|
||||
|
||||
val syscall =
|
||||
when (arrayName.type) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> IMSyscall.ANY_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
DataType.ARRAY_W -> IMSyscall.ANY_WORD
|
||||
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(arrayName)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcAll(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
|
||||
if(arrayName.type in SplitWordArrayTypes) {
|
||||
// this is a bit complicated to calculate.... have to check all recombined (lsb,msb) words for $0000
|
||||
TODO("all(split words $arrayName)")
|
||||
}
|
||||
|
||||
val syscall =
|
||||
when(arrayName.type) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> IMSyscall.ALL_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
DataType.ARRAY_W -> IMSyscall.ALL_WORD
|
||||
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(arrayName)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcAbs(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val sourceDt = call.args.single().type
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
@@ -369,65 +318,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
if(arrayName.type in SplitWordArrayTypes) {
|
||||
// reverse the lsb and msb arrays both, independently
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
|
||||
addToResult(result, trLsb, trLsb.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
|
||||
addToResult(result, trMsb, trMsb.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
val syscall =
|
||||
when(arrayName.type) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
|
||||
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
|
||||
else -> throw IllegalArgumentException("weird type to reverse")
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(arrayName)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcSort(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val syscall =
|
||||
when(arrayName.type) {
|
||||
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
|
||||
DataType.ARRAY_B -> IMSyscall.SORT_BYTE
|
||||
DataType.ARRAY_UW -> IMSyscall.SORT_UWORD
|
||||
DataType.ARRAY_W -> IMSyscall.SORT_WORD
|
||||
DataType.STR -> IMSyscall.SORT_UBYTE
|
||||
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
||||
in SplitWordArrayTypes -> TODO("split word sort")
|
||||
else -> throw IllegalArgumentException("weird type to sort")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(arrayName)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
@@ -714,6 +604,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val arr = (arg as? PtArrayIndexer)
|
||||
val index = arr?.index?.asConstInteger()
|
||||
if(arr!=null && index!=null) {
|
||||
if(arr.splitWords) TODO("IR rol/ror on split words array")
|
||||
val variable = arr.variable.name
|
||||
val itemsize = codeGen.program.memsizer.memorySize(arr.type)
|
||||
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = index*itemsize), null)
|
||||
@@ -854,10 +745,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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StNode
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StSub
|
||||
import prog8.code.ast.*
|
||||
@@ -7,8 +8,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 +28,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 -> {
|
||||
@@ -164,55 +168,93 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
private fun translate(check: PtContainmentCheck): ExpressionCodeResult {
|
||||
val elementDt = check.needle.type
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
when(check.iterable.type) {
|
||||
|
||||
if(check.haystackValues!=null) {
|
||||
val haystack = check.haystackValues!!.children.map {
|
||||
if(it is PtBool) it.asInt()
|
||||
else (it as PtNumber).number.toInt()
|
||||
}
|
||||
when(elementDt) {
|
||||
in IntegerDatatypesWithBoolean -> {
|
||||
if (elementDt in ByteDatatypesWithBoolean) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE)
|
||||
if (elementDt in WordDatatypes) require(haystack.size in 0..PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_WORD)
|
||||
val gottemLabel = codeGen.createLabelName()
|
||||
val endLabel = codeGen.createLabelName()
|
||||
val elementTr = translateExpression(check.needle)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
for(value in haystack){
|
||||
it += IRInstruction(Opcode.CMPI, irType(elementDt), elementTr.resultReg, immediate = value)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = gottemLabel)
|
||||
}
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, elementTr.resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = endLabel)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, elementTr.resultReg, immediate = 1), gottemLabel)
|
||||
result += IRCodeChunk(endLabel, null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
|
||||
}
|
||||
DataType.FLOAT -> throw AssemblyError("containmentchecks for floats should always be done on an array variable with subroutine")
|
||||
else -> throw AssemblyError("weird dt $elementDt")
|
||||
}
|
||||
}
|
||||
|
||||
val haystackVar = check.haystackHeapVar!!
|
||||
when(haystackVar.type) {
|
||||
DataType.STR -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
val elementTr = translateExpression(check.needle)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
val iterableTr = translateExpression(haystackVar)
|
||||
addToResult(result, iterableTr, iterableTr.resultReg, -1)
|
||||
result += codeGen.makeSyscall(IMSyscall.STRING_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg), IRDataType.BYTE to elementTr.resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
val elementTr = translateExpression(check.needle)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
val iterableTr = translateExpression(haystackVar)
|
||||
addToResult(result, iterableTr, iterableTr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
|
||||
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
val elementTr = translateExpression(check.needle)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
val iterableTr = translateExpression(haystackVar)
|
||||
addToResult(result, iterableTr, iterableTr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
|
||||
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=elementTr.resultReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
val elementTr = translateExpression(check.needle)
|
||||
addToResult(result, elementTr, -1, elementTr.resultFpReg)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
val iterableTr = translateExpression(haystackVar)
|
||||
addToResult(result, iterableTr, iterableTr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
val iterableLength = codeGen.symbolTable.getLength(check.iterable.name)
|
||||
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
else -> throw AssemblyError("weird iterable dt ${check.iterable.type} for ${check.iterable.name}")
|
||||
else -> throw AssemblyError("weird iterable dt ${haystackVar.type} for ${haystackVar.name}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +490,36 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
|
||||
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
|
||||
val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)
|
||||
|
||||
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw")) {
|
||||
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
|
||||
return translateStackFunctions(fcall, callTarget)
|
||||
}
|
||||
when(callTarget.scopedName) {
|
||||
"sys.clear_carry" -> {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(chunk, IRInstruction(Opcode.CLC), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
"sys.set_carry" -> {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(chunk, IRInstruction(Opcode.SEC), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
"sys.clear_irqd" -> {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(chunk, IRInstruction(Opcode.CLI), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
"sys.set_irqd" -> {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(chunk, IRInstruction(Opcode.SEI), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
}
|
||||
|
||||
when (callTarget) {
|
||||
is StSub -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
|
||||
@@ -463,7 +534,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 +543,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 +565,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 +658,62 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateStackFunctions(fcall: PtFunctionCall, callTarget: StNode): ExpressionCodeResult {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
when(callTarget.scopedName) {
|
||||
"sys.push" -> {
|
||||
// push byte
|
||||
val tr = translateExpression(fcall.args.single())
|
||||
chunk += tr.chunks
|
||||
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
"sys.pushw" -> {
|
||||
// push word
|
||||
val tr = translateExpression(fcall.args.single())
|
||||
chunk += tr.chunks
|
||||
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.WORD, -1, -1)
|
||||
}
|
||||
"sys.pop" -> {
|
||||
// pop byte
|
||||
val popReg = codeGen.registers.nextFree()
|
||||
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=popReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, popReg, -1)
|
||||
}
|
||||
"sys.popw" -> {
|
||||
// pop word
|
||||
val popReg = codeGen.registers.nextFree()
|
||||
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=popReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.WORD, popReg, -1)
|
||||
}
|
||||
else -> throw AssemblyError("unknown stack subroutine called")
|
||||
}
|
||||
}
|
||||
|
||||
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 +887,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.intermediate.*
|
||||
import kotlin.io.path.readBytes
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
class IRCodeGen(
|
||||
@@ -52,9 +51,6 @@ class IRCodeGen(
|
||||
optimizer.optimize(options.optimize, errors)
|
||||
irProg.validate()
|
||||
|
||||
val regOptimizer = IRRegisterOptimizer(irProg)
|
||||
regOptimizer.optimize()
|
||||
|
||||
return irProg
|
||||
}
|
||||
|
||||
@@ -394,10 +390,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)
|
||||
@@ -491,49 +487,76 @@ class IRCodeGen(
|
||||
val loopLabel = createLabelName()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
val toTr = expressionEval.translateExpression(iterable.to)
|
||||
addToResult(result, toTr, toTr.resultReg, -1)
|
||||
val fromTr = expressionEval.translateExpression(iterable.from)
|
||||
addToResult(result, fromTr, fromTr.resultReg, -1)
|
||||
|
||||
val labelAfterFor = createLabelName()
|
||||
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
|
||||
if(step>0)
|
||||
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
|
||||
else
|
||||
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
|
||||
} else {
|
||||
if(step>0)
|
||||
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
|
||||
else
|
||||
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
|
||||
}
|
||||
addInstr(result, precheckInstruction, null)
|
||||
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
|
||||
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
||||
if(step==1 || step==-1) {
|
||||
// if endvalue == loopvar, stop loop, else iterate
|
||||
if(loopvarDtIr==IRDataType.BYTE && step==-1 && iterable.to.asConstInteger()==0) {
|
||||
// downto 0 optimization (byte)
|
||||
val fromTr = expressionEval.translateExpression(iterable.from)
|
||||
addToResult(result, fromTr, fromTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
|
||||
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
|
||||
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
|
||||
it += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = fromTr.resultReg, immediate = 255)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
|
||||
} else {
|
||||
// ind/dec index, then:
|
||||
// ascending: if endvalue >= loopvar, iterate
|
||||
// descending: if loopvar >= endvalue, iterate
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
|
||||
if(step > 0)
|
||||
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
|
||||
}
|
||||
result += IRCodeChunk(labelAfterFor, null)
|
||||
else if(step==-1 && iterable.to.asConstInteger()==1) {
|
||||
// downto 1 optimization (byte and word)
|
||||
val fromTr = expressionEval.translateExpression(iterable.from)
|
||||
addToResult(result, fromTr, fromTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
|
||||
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val toTr = expressionEval.translateExpression(iterable.to)
|
||||
addToResult(result, toTr, toTr.resultReg, -1)
|
||||
val fromTr = expressionEval.translateExpression(iterable.from)
|
||||
addToResult(result, fromTr, fromTr.resultReg, -1)
|
||||
|
||||
val labelAfterFor = createLabelName()
|
||||
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
|
||||
if(step>0)
|
||||
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
|
||||
else
|
||||
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
|
||||
} else {
|
||||
if(step>0)
|
||||
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
|
||||
else
|
||||
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
|
||||
}
|
||||
addInstr(result, precheckInstruction, null)
|
||||
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
|
||||
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
||||
if(step==1 || step==-1) {
|
||||
// if endvalue == loopvar, stop loop, else iterate
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
|
||||
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
|
||||
}
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
|
||||
} else {
|
||||
// ind/dec index, then:
|
||||
// ascending: if endvalue >= loopvar, iterate
|
||||
// descending: if loopvar >= endvalue, iterate
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
|
||||
if(step > 0)
|
||||
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
|
||||
}
|
||||
result += IRCodeChunk(labelAfterFor, null)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -562,9 +585,25 @@ class IRCodeGen(
|
||||
result += chunk
|
||||
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
||||
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
|
||||
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
|
||||
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
|
||||
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
if(loopvarDtIr==IRDataType.BYTE && iterable.step==-1 && iterable.last==0) {
|
||||
// downto 0 optimization (byte)
|
||||
if(loopvarDt==DataType.BYTE || iterable.first<=127) {
|
||||
chunk2 += IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel)
|
||||
} else {
|
||||
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
|
||||
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
|
||||
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
}
|
||||
else if(iterable.step==-1 && iterable.last==1) {
|
||||
// downto 1 optimization (byte and word)
|
||||
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
} else {
|
||||
// downto some other value
|
||||
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
|
||||
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
|
||||
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
result += chunk2
|
||||
return result
|
||||
}
|
||||
@@ -685,13 +724,11 @@ class IRCodeGen(
|
||||
return code
|
||||
}
|
||||
|
||||
internal val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() }
|
||||
|
||||
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
val pow2 = powersOfTwoInt.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
|
||||
@@ -715,7 +752,7 @@ class IRCodeGen(
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
val pow2 = powersOfTwoInt.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += if(knownAddress!=null)
|
||||
@@ -788,19 +825,32 @@ class IRCodeGen(
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1 && !signed) {
|
||||
code += IRInstruction(Opcode.LSR, dt, reg1=reg) // simple single bit shift
|
||||
}
|
||||
else if(pow2>=1 &&!signed) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
code += if(signed)
|
||||
IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
|
||||
else
|
||||
IRInstruction(Opcode.LSRN, dt, reg1=reg, reg2=pow2reg)
|
||||
val pow2 = powersOfTwoInt.indexOf(factor)
|
||||
if(pow2>=0) {
|
||||
if(signed) {
|
||||
if(pow2==1) {
|
||||
// simple single bit shift (signed)
|
||||
code += IRInstruction(Opcode.ASR, dt, reg1=reg)
|
||||
} else {
|
||||
// just shift multiple bits (signed)
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
code += IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
|
||||
}
|
||||
} else {
|
||||
if(pow2==1) {
|
||||
// simple single bit shift (unsigned)
|
||||
code += IRInstruction(Opcode.LSR, dt, reg1=reg)
|
||||
} else {
|
||||
// just shift multiple bits (unsigned)
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
|
||||
code += IRInstruction(Opcode.LSRN, dt, reg1 = reg, reg2 = pow2reg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
} else {
|
||||
// regular div
|
||||
code += if (factor == 0) {
|
||||
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff)
|
||||
} else {
|
||||
@@ -809,39 +859,56 @@ class IRCodeGen(
|
||||
else
|
||||
IRInstruction(Opcode.DIV, dt, reg1=reg, immediate = factor)
|
||||
}
|
||||
return code
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun divideByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int, signed: Boolean): IRCodeChunk {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1 && !signed) {
|
||||
// just simple bit shift
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRM, dt, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol)
|
||||
val pow2 = powersOfTwoInt.indexOf(factor)
|
||||
if(pow2>=0) {
|
||||
// can do bit shift instead of division
|
||||
if(signed) {
|
||||
if(pow2==1) {
|
||||
// just simple bit shift (signed)
|
||||
code += if (knownAddress != null)
|
||||
IRInstruction(Opcode.ASRM, dt, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.ASRM, dt, labelSymbol = symbol)
|
||||
} else {
|
||||
// just shift multiple bits (signed)
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
|
||||
code += if (knownAddress != null)
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
||||
}
|
||||
} else {
|
||||
if(pow2==1) {
|
||||
// just simple bit shift (unsigned)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRM, dt, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol)
|
||||
}
|
||||
else {
|
||||
// just shift multiple bits (unsigned)
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
else if(pow2>=1 && !signed) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
code += if(signed) {
|
||||
if(knownAddress!=null)
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
||||
}
|
||||
else {
|
||||
if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
||||
}
|
||||
} else {
|
||||
else
|
||||
{
|
||||
// regular div
|
||||
if (factor == 0) {
|
||||
val reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff)
|
||||
@@ -866,8 +933,8 @@ class IRCodeGen(
|
||||
IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(ifElse: PtIfElse): IRCodeChunks {
|
||||
@@ -1294,6 +1361,10 @@ class IRCodeGen(
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode) {
|
||||
|
||||
if(condition is PtBuiltinFunctionCall && condition.name.startsWith("prog8_ifelse_bittest_"))
|
||||
throw AssemblyError("IR codegen doesn't have special instructions for dedicated BIT tests and should just still use normal AND")
|
||||
|
||||
val tr = expressionEval.translateExpression(condition)
|
||||
result += tr.chunks
|
||||
if(ifElse.hasElse()) {
|
||||
@@ -1451,6 +1522,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 +1577,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
|
||||
@@ -1617,7 +1691,7 @@ class IRCodeGen(
|
||||
|
||||
private fun translate(parameters: List<PtSubroutineParameter>) =
|
||||
parameters.map {
|
||||
val flattenedName = it.definingSub()!!.name + "." + it.name
|
||||
val flattenedName = it.definingISub()!!.name + "." + it.name
|
||||
if(symbolTable.lookup(flattenedName)==null)
|
||||
TODO("fix missing lookup for: $flattenedName parameter")
|
||||
val orig = symbolTable.lookup(flattenedName) as StStaticVariable
|
||||
@@ -1650,7 +1724,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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -267,6 +263,24 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ins.opcode== Opcode.SEI || ins.opcode== Opcode.CLI) {
|
||||
if(idx < chunk.instructions.size-1) {
|
||||
val insAfter = chunk.instructions[idx+1]
|
||||
if(insAfter.opcode == ins.opcode) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
else if(ins.opcode== Opcode.SEI && insAfter.opcode== Opcode.CLI) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
else if(ins.opcode== Opcode.CLI && insAfter.opcode== Opcode.SEI) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
@@ -433,21 +447,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
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -80,21 +80,52 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
instr[1].opcode shouldBe Opcode.INC
|
||||
}
|
||||
|
||||
test("remove double sec/clc") {
|
||||
test("remove double sec/clc/sei/cli") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.CLC)
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 6
|
||||
irProg.chunks().single().instructions.size shouldBe 12
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 1
|
||||
instr.size shouldBe 2
|
||||
instr[0].opcode shouldBe Opcode.CLC
|
||||
instr[1].opcode shouldBe Opcode.CLI
|
||||
}
|
||||
|
||||
test("remove double sec/clc/sei/cli reversed") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 12
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 2
|
||||
instr[0].opcode shouldBe Opcode.SEC
|
||||
instr[1].opcode shouldBe Opcode.SEI
|
||||
}
|
||||
|
||||
test("push followed by pop") {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class ConstExprEvaluator {
|
||||
// DataType.WORD -> result = result.toShort().toInt()
|
||||
// else -> { /* keep as it is */ }
|
||||
// }
|
||||
return NumericLiteral.optimalNumeric(result.toDouble(), left.position)
|
||||
return NumericLiteral.optimalNumeric(left.type, null, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
@@ -133,7 +133,7 @@ class ConstExprEvaluator {
|
||||
val error = "cannot add $left and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() + right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@@ -150,7 +150,7 @@ class ConstExprEvaluator {
|
||||
val error = "cannot subtract $left and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() - right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@@ -167,7 +167,7 @@ class ConstExprEvaluator {
|
||||
val error = "cannot multiply ${left.type} and ${right.type}"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(left.type, right.type, left.number.toInt() * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() * right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@@ -190,7 +190,7 @@ class ConstExprEvaluator {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
val result: Int = left.number.toInt() / right.number.toInt()
|
||||
NumericLiteral.optimalInteger(result, left.position)
|
||||
NumericLiteral.optimalInteger(left.type, right.type, result, left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number==0.0) divideByZeroError(right.position)
|
||||
@@ -219,7 +219,7 @@ class ConstExprEvaluator {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteral.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
|
||||
NumericLiteral.optimalNumeric(left.type, right.type, left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
|
||||
@@ -33,6 +33,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
}
|
||||
|
||||
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(numLiteral.type==DataType.LONG) {
|
||||
// see if LONG values may be reduced to something smaller
|
||||
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
|
||||
if(smaller.type!=DataType.LONG)
|
||||
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
|
||||
}
|
||||
|
||||
if(parent is Assignment) {
|
||||
val iDt = parent.target.inferType(program)
|
||||
if(iDt.isKnown && !iDt.isBool && !iDt.istype(numLiteral.type)) {
|
||||
@@ -91,7 +99,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
program.encoding.encodeString(leftString.value, leftString.encoding) + program.encoding.encodeString(rightString.value, rightString.encoding),
|
||||
leftString.encoding)
|
||||
}
|
||||
val concatStr = StringLiteral(concatenated, leftString.encoding, expr.position)
|
||||
val concatStr = StringLiteral.create(concatenated, leftString.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, concatStr, parent))
|
||||
}
|
||||
else if(expr.operator=="*" && rightconst!=null && expr.left is StringLiteral) {
|
||||
@@ -99,7 +107,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
val part = expr.left as StringLiteral
|
||||
if(part.value.isEmpty())
|
||||
errors.warn("resulting string has length zero", part.position)
|
||||
val newStr = StringLiteral(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
|
||||
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
|
||||
}
|
||||
}
|
||||
@@ -164,13 +172,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
if(leftconst==null && rightconst!=null && rightconst.number<0.0) {
|
||||
if (expr.operator == "-") {
|
||||
// X - -1 ---> X + 1
|
||||
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position)
|
||||
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
|
||||
val plusExpr = BinaryExpression(expr.left, "+", posNumber, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
|
||||
}
|
||||
else if (expr.operator == "+") {
|
||||
// X + -1 ---> X - 1
|
||||
val posNumber = NumericLiteral.optimalNumeric(-rightconst.number, rightconst.position)
|
||||
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
|
||||
val plusExpr = BinaryExpression(expr.left, "-", posNumber, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
|
||||
}
|
||||
@@ -384,12 +392,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
val numval = decl.value as? NumericLiteral
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
if(valueDt istype DataType.LONG) {
|
||||
return noModifications // this is handled in the numericalvalue case
|
||||
}
|
||||
if(valueDt isnot decl.datatype) {
|
||||
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
|
||||
val cast = numval.cast(decl.datatype, true)
|
||||
if (cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
}
|
||||
val cast = numval.cast(decl.datatype, true)
|
||||
if (cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@@ -435,7 +444,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.
|
||||
|
||||
@@ -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)
|
||||
@@ -392,11 +392,35 @@ internal class ConstantIdentifierReplacer(
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// convert a range expression that is assigned to an array, to an array literal instead.
|
||||
val range = assignment.value as? RangeExpression
|
||||
if(range!=null) {
|
||||
val targetDatatype = assignment.target.inferType(program)
|
||||
if(targetDatatype.isArray) {
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, targetDatatype.getOr(DataType.UNDEFINED),
|
||||
ZeropageWish.DONTCARE, null, "dummy", emptyList(),
|
||||
assignment.value, false, false, Position.DUMMY)
|
||||
val replaceValue = createConstArrayInitializerValue(decl)
|
||||
if(replaceValue!=null) {
|
||||
return listOf(IAstModification.ReplaceNode(assignment.value, replaceValue, assignment))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
|
||||
|
||||
if(decl.type==VarDeclType.MEMORY)
|
||||
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
|
||||
|
||||
val rangeSize=(decl.value as? RangeExpression)?.size()
|
||||
if(rangeSize!=null && rangeSize>65535) {
|
||||
errors.err("range size overflow", decl.value!!.position)
|
||||
return null
|
||||
}
|
||||
|
||||
// convert the initializer range expression from a range or int, to an actual array.
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
|
||||
@@ -490,8 +514,7 @@ internal class ConstantIdentifierReplacer(
|
||||
if(numericLv!=null) {
|
||||
// arraysize initializer is a single value, and we know the array size.
|
||||
if(numericLv.type!=DataType.BOOL) {
|
||||
if(options.strictBool || numericLv.type !in ByteDatatypes)
|
||||
errors.err("initializer value is not a boolean", numericLv.position)
|
||||
errors.err("initializer value is not a boolean", numericLv.position)
|
||||
return null
|
||||
}
|
||||
val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
|
||||
@@ -4,7 +4,6 @@ import prog8.ast.*
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.Jump
|
||||
import prog8.ast.walk.AstWalker
|
||||
@@ -12,14 +11,9 @@ import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
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()
|
||||
|
||||
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
|
||||
@@ -107,13 +101,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if (!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
||||
|
||||
// NonBinaryExpression <associativeoperator> BinaryExpression --> BinaryExpression <associativeoperator> NonBinaryExpression
|
||||
if (expr.operator in AssociativeOperators && expr.left !is BinaryExpression && expr.right is BinaryExpression) {
|
||||
if(parent !is Assignment || !(expr.left isSameAs parent.target) && maySwapOperandOrder(expr))
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
}
|
||||
return noModifications
|
||||
|
||||
// X + (-A) --> X - A
|
||||
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
|
||||
@@ -249,26 +237,24 @@ 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) {
|
||||
if (rightDt != leftDt) {
|
||||
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
} else
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
if (rightDt != leftDt) {
|
||||
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
else if (rightVal?.number == 0.0) {
|
||||
if (options.strictBool) {
|
||||
if (rightDt != leftDt) {
|
||||
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
if (rightDt != leftDt) {
|
||||
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,21 +269,16 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
}
|
||||
}
|
||||
if (rightVal?.number == 1.0) {
|
||||
if(options.strictBool) {
|
||||
if(rightDt!=leftDt) {
|
||||
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
if(rightDt!=leftDt) {
|
||||
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
else if (rightVal?.number == 0.0) {
|
||||
if(options.strictBool) {
|
||||
if(rightDt!=leftDt) {
|
||||
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
} else
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
if(rightDt!=leftDt) {
|
||||
val right = NumericLiteral(leftDt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,7 +673,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteral(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
|
||||
} else if (cv in powersOfTwo) {
|
||||
} else if (cv in powersOfTwoFloat) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
return null
|
||||
@@ -717,6 +698,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
return null
|
||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||
when (cv) {
|
||||
0.0 -> return null // fall through to regular float division to properly deal with division by zero
|
||||
-1.0 -> {
|
||||
// '/' -> -left
|
||||
if (expr.operator == "/") {
|
||||
@@ -744,12 +726,10 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
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???
|
||||
// so we leave that as is and let the code generator deal with it.
|
||||
val numshifts = log2(cv).toInt()
|
||||
in powersOfTwoFloat -> {
|
||||
val numshifts = powersOfTwoFloat.indexOf(cv)
|
||||
if (leftDt in IntegerDatatypes) {
|
||||
// division by a power of two => shift right (signed and unsigned)
|
||||
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
@@ -801,14 +781,14 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
// left
|
||||
return expr2.left
|
||||
}
|
||||
in powersOfTwo -> {
|
||||
in powersOfTwoFloat -> {
|
||||
if (leftValue.inferType(program).isInteger) {
|
||||
// times a power of two => shift left
|
||||
val numshifts = log2(cv).toInt()
|
||||
return BinaryExpression(expr2.left, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
in negativePowersOfTwo -> {
|
||||
in negativePowersOfTwoFloat -> {
|
||||
if (leftValue.inferType(program).isInteger) {
|
||||
// times a negative power of two => negate, then shift
|
||||
val numshifts = log2(-cv).toInt()
|
||||
|
||||
@@ -59,8 +59,8 @@ fun Program.inlineSubroutines(options: CompilationOptions): Int {
|
||||
return inliner.applyModifications()
|
||||
}
|
||||
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter, options: CompilationOptions) : Int {
|
||||
val opti = ExpressionSimplifier(this, options, errors)
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
@@ -30,11 +30,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine}
|
||||
if(!containsSubsOrVariables) {
|
||||
if(subroutine.statements.size==1 || (subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))) {
|
||||
if(subroutine !== program.entrypoint) {
|
||||
if (!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine }
|
||||
if (!containsSubsOrVariables) {
|
||||
if (subroutine.statements.size == 1 || (subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))) {
|
||||
if (subroutine !== program.entrypoint) {
|
||||
// subroutine is possible candidate to be inlined
|
||||
subroutine.inline =
|
||||
when (val stmt = subroutine.statements[0]) {
|
||||
@@ -87,9 +87,9 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
} else
|
||||
false
|
||||
targetInline || valueInline
|
||||
} else if(stmt.target.identifier!=null && stmt.isAugmentable) {
|
||||
} else if (stmt.target.identifier != null && stmt.isAugmentable) {
|
||||
val binExpr = stmt.value as BinaryExpression
|
||||
if(binExpr.operator in "+-" && binExpr.right.constValue(program)?.number==1.0) {
|
||||
if (binExpr.operator in "+-" && binExpr.right.constValue(program)?.number == 1.0) {
|
||||
makeFullyScoped(stmt.target.identifier!!)
|
||||
makeFullyScoped(binExpr.left as IdentifierReference)
|
||||
true
|
||||
@@ -121,8 +121,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine.inline && subroutine.statements.size>1) {
|
||||
require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))
|
||||
if (subroutine.inline && subroutine.statements.size > 1) {
|
||||
require(subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))
|
||||
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
|
||||
}
|
||||
}
|
||||
@@ -147,6 +147,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: FunctionCallStatement) {
|
||||
makeFullyScoped(call.target)
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
@@ -169,6 +170,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: FunctionCallExpression) {
|
||||
makeFullyScoped(call.target)
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
@@ -222,8 +224,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
noModifications
|
||||
}
|
||||
else -> {
|
||||
sub.hasBeenInlined=true
|
||||
listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent))
|
||||
if(origNode !== toInline) {
|
||||
sub.hasBeenInlined = true
|
||||
listOf(IAstModification.ReplaceNode(origNode, toInline.copy(), parent))
|
||||
} else
|
||||
noModifications
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +258,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
is Return -> {
|
||||
// is an expression, so we have to have a Return here in the inlined sub
|
||||
// note that we don't have to process any args, because we online inline parameterless subroutines.
|
||||
if(toInline.value!=null) {
|
||||
if(toInline.value!=null && functionCallExpr!==toInline.value) {
|
||||
sub.hasBeenInlined=true
|
||||
listOf(IAstModification.ReplaceNode(functionCallExpr, toInline.value!!.copy(), parent))
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ class StatementOptimizer(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource.size==1) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in functions.purefunctionNames) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
if("ignore_unused" !in parent.definingBlock.options())
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
@@ -162,7 +163,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 +178,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 +191,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))
|
||||
@@ -199,35 +200,6 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val loopvarDt = forLoop.loopVarDt(program)
|
||||
if(loopvarDt.istype(DataType.UWORD) || loopvarDt.istype(DataType.UBYTE)) {
|
||||
|
||||
fun incOrDec(inc: Boolean): Assignment {
|
||||
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)
|
||||
}
|
||||
|
||||
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
|
||||
val fromExpr = range.from
|
||||
if(fromExpr.constValue(program)==null) {
|
||||
// FOR X = something DOWNTO 0 {...} --> X=something, DO { ... , X-- } UNTIL X=255 (or 65535 if uword)
|
||||
val pos = forLoop.position
|
||||
val checkValue = NumericLiteral(loopvarDt.getOr(DataType.UNDEFINED), if(loopvarDt.istype(DataType.UBYTE)) 255.0 else 65535.0, pos)
|
||||
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", checkValue, pos)
|
||||
val decOne = incOrDec(false)
|
||||
forLoop.body.statements.add(decOne)
|
||||
val replacement = AnonymousScope(mutableListOf(
|
||||
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
|
||||
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
|
||||
UntilLoop(forLoop.body, condition, pos)
|
||||
), pos)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -454,12 +426,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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -149,7 +149,8 @@ class UnusedCodeRemover(private val program: Program,
|
||||
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
|
||||
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
|
||||
if(declIndex==singleUseIndex-1) {
|
||||
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
|
||||
if("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
|
||||
val fcall = assignment.value as IFunctionCall
|
||||
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
|
||||
return listOf(
|
||||
|
||||
@@ -2,14 +2,14 @@ plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
// id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
id 'io.github.goooler.shadow' version '8.1.7'
|
||||
id 'com.peterabeles.gversion' version '1.10.2'
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
targetCompatibility = JavaLanguageVersion.of(javaVersion)
|
||||
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
@@ -32,15 +32,16 @@ dependencies {
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation project(':virtualmachine')
|
||||
implementation "org.antlr:antlr4-runtime:4.13.1"
|
||||
implementation "org.antlr:antlr4-runtime:4.13.2"
|
||||
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.1'
|
||||
testImplementation 'io.kotest:kotest-framework-datatest:5.9.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="module" module-name="codeOptimizers" />
|
||||
@@ -24,5 +24,6 @@
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
<orderEntry type="module" module-name="intermediate" scope="TEST" />
|
||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,82 @@
|
||||
; any() and all() checks on arrays/memory buffers.
|
||||
; These were builtin functions in older versions of the language.
|
||||
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
anyall {
|
||||
sub any(uword arrayptr, uword num_elements) -> bool {
|
||||
; -- returns true if any byte in the array is not zero.
|
||||
cx16.r1 = arrayptr
|
||||
if msb(num_elements)==0 {
|
||||
for cx16.r0L in 0 to lsb(num_elements)-1 {
|
||||
if cx16.r1[cx16.r0L]!=0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
repeat num_elements {
|
||||
if @(cx16.r1)!=0
|
||||
return true
|
||||
cx16.r1++
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub all(uword arrayptr, uword num_elements) -> bool {
|
||||
; -- returns true if all bytes in the array are not zero.
|
||||
cx16.r1 = arrayptr
|
||||
if msb(num_elements)==0 {
|
||||
for cx16.r0L in 0 to lsb(num_elements)-1 {
|
||||
if cx16.r1[cx16.r0L]==0
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
repeat num_elements {
|
||||
if @(cx16.r1)==0
|
||||
return false
|
||||
cx16.r1++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub anyw(uword arrayptr, uword num_elements) -> bool {
|
||||
; -- returns true if any word in the array is not zero.
|
||||
; doesn't work on @split arrays.
|
||||
cx16.r1 = arrayptr
|
||||
if msb(num_elements)==0 {
|
||||
repeat lsb(num_elements) {
|
||||
if peekw(cx16.r1)!=0
|
||||
return true
|
||||
cx16.r1+=2
|
||||
}
|
||||
return false
|
||||
}
|
||||
repeat num_elements {
|
||||
if peekw(cx16.r1)!=0
|
||||
return true
|
||||
cx16.r1+=2
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub allw(uword arrayptr, uword num_elements) -> bool {
|
||||
; -- returns true if all words in the array are not zero.
|
||||
; doesn't work on @split arrays.
|
||||
cx16.r1 = arrayptr
|
||||
if msb(num_elements)==0 {
|
||||
repeat lsb(num_elements) {
|
||||
if peekw(cx16.r1)==0
|
||||
return false
|
||||
cx16.r1+=2
|
||||
}
|
||||
return true
|
||||
}
|
||||
repeat num_elements {
|
||||
if peekw(cx16.r1)==0
|
||||
return false
|
||||
cx16.r1+=2
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -23,16 +23,20 @@ sys {
|
||||
|
||||
const ubyte target = 8 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL
|
||||
|
||||
const ubyte sizeof_bool = 1
|
||||
const ubyte sizeof_byte = 1
|
||||
const ubyte sizeof_ubyte = 1
|
||||
const ubyte sizeof_word = 2
|
||||
const ubyte sizeof_uword = 2
|
||||
const ubyte sizeof_float = 0 ; undefined, no float support
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; TODO
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
clc
|
||||
; TODO reset screen mode etc etc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
@@ -40,7 +44,26 @@ sys {
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
rts ; no phase 2 steps on the Atari
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
%asm {{
|
||||
_exitcodeCarry = *+1
|
||||
lda #0
|
||||
lsr a
|
||||
_exitcode = *+1
|
||||
lda #0 ; exit code possibly modified in exit()
|
||||
_exitcodeX = *+1
|
||||
ldx #0
|
||||
_exitcodeY = *+1
|
||||
ldy #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -248,27 +271,38 @@ save_SCRATCH_ZPWORD2 .word 0
|
||||
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
; TODO where to store A as exit code?
|
||||
%asm {{
|
||||
sta cleanup_at_exit._exitcode
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
jmp cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit2(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers.
|
||||
; TODO where to store A,X,Y as exit code?
|
||||
%asm {{
|
||||
jmp exit
|
||||
sta cleanup_at_exit._exitcode
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit3(ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc) {
|
||||
; -- immediately exit the program with result values in the A, X and Y registers, and the Carry flag in the status register.
|
||||
; TODO where to store A,X,Y,Carry as exit code?
|
||||
%asm {{
|
||||
jmp exit
|
||||
sta cleanup_at_exit._exitcode
|
||||
lda #0
|
||||
rol a
|
||||
sta cleanup_at_exit._exitcodeCarry
|
||||
stx cleanup_at_exit._exitcodeX
|
||||
sty cleanup_at_exit._exitcodeY
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
jmp cleanup_at_exit
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
; **experimental** buffer data structures, API subject to change!!
|
||||
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
smallringbuffer {
|
||||
; -- A ringbuffer (FIFO queue) that occupies a single page in memory, containing 255 bytes maximum.
|
||||
; You can store and retrieve words too.
|
||||
; It's optimized for speed and depends on the byte-wrap-around feature when doing incs and decs.
|
||||
|
||||
ubyte fill
|
||||
ubyte head
|
||||
ubyte tail
|
||||
ubyte[256] buffer
|
||||
|
||||
sub init() {
|
||||
; -- (re)initialize the ringbuffer, you must call this before using the other routines
|
||||
head = fill = 0
|
||||
tail = 255
|
||||
}
|
||||
|
||||
sub put(ubyte value) -> bool {
|
||||
; -- store a byte in the buffer, returns success
|
||||
if fill==255
|
||||
return false
|
||||
buffer[head] = value
|
||||
head++
|
||||
fill++
|
||||
return true
|
||||
}
|
||||
|
||||
sub putw(uword value) -> bool {
|
||||
; -- store a word in the buffer, returns success
|
||||
if fill>=254
|
||||
return false
|
||||
fill += 2
|
||||
buffer[head] = lsb(value)
|
||||
head++
|
||||
buffer[head] = msb(value)
|
||||
head++
|
||||
return true
|
||||
}
|
||||
|
||||
sub get() -> ubyte {
|
||||
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
|
||||
if fill==0 {
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
}
|
||||
fill--
|
||||
tail++
|
||||
sys.set_carry()
|
||||
return buffer[tail]
|
||||
}
|
||||
|
||||
sub getw() -> uword {
|
||||
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
|
||||
if fill<2 {
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
}
|
||||
fill -= 2
|
||||
tail++
|
||||
cx16.r0L = buffer[tail]
|
||||
tail++
|
||||
cx16.r0H = buffer[tail]
|
||||
sys.set_carry()
|
||||
return cx16.r0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ringbuffer {
|
||||
; -- A ringbuffer (FIFO queue) that occupies a single page in memory, containing 8 KB maximum.
|
||||
; You can store and retrieve words too.
|
||||
|
||||
uword fill
|
||||
uword head
|
||||
uword tail
|
||||
uword buffer_ptr = memory("ringbuffer", 8192, 0)
|
||||
|
||||
sub init() {
|
||||
; -- (re)initialize the ringbuffer, you must call this before using the other routines
|
||||
head = fill = 0
|
||||
tail = 8191
|
||||
}
|
||||
|
||||
sub put(ubyte value) -> bool {
|
||||
; -- store a byte in the buffer, returns success
|
||||
if fill==8192
|
||||
return false
|
||||
buffer_ptr[head] = value
|
||||
inc_head()
|
||||
fill++
|
||||
return true
|
||||
}
|
||||
|
||||
sub putw(uword value) -> bool {
|
||||
; -- store a word in the buffer, returns success
|
||||
if fill>=8191
|
||||
return false
|
||||
fill += 2
|
||||
buffer_ptr[head] = lsb(value)
|
||||
inc_head()
|
||||
buffer_ptr[head] = msb(value)
|
||||
inc_head()
|
||||
return true
|
||||
}
|
||||
|
||||
sub get() -> ubyte {
|
||||
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
|
||||
if fill==0 {
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
}
|
||||
fill--
|
||||
inc_tail()
|
||||
cx16.r0L = buffer_ptr[tail]
|
||||
sys.set_carry()
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
sub getw() -> uword {
|
||||
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
|
||||
if fill<2 {
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
}
|
||||
fill -= 2
|
||||
inc_tail()
|
||||
cx16.r0L = buffer_ptr[tail]
|
||||
inc_tail()
|
||||
cx16.r0H = buffer_ptr[tail]
|
||||
sys.set_carry()
|
||||
return cx16.r0
|
||||
}
|
||||
|
||||
sub inc_head() {
|
||||
head++
|
||||
if msb(head)==$20
|
||||
head=0
|
||||
}
|
||||
|
||||
sub inc_tail() {
|
||||
tail++
|
||||
if msb(tail)==$20
|
||||
tail=0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; TODO ringbuffer (FIFO queue) should use banked ram on the X16, but still work on virtual
|
||||
; TODO stack (LIFO queue) using more than 1 page of ram (maybe even banked ram on the x16)
|
||||
@@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -345,6 +351,14 @@ sys {
|
||||
|
||||
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
const ubyte sizeof_bool = 1
|
||||
const ubyte sizeof_byte = 1
|
||||
const ubyte sizeof_ubyte = 1
|
||||
const ubyte sizeof_word = 2
|
||||
const ubyte sizeof_uword = 2
|
||||
const ubyte sizeof_float = 5
|
||||
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
@@ -354,7 +368,6 @@ asmsub init_system() {
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
lda #0
|
||||
sta $ff00 ; select default bank 15
|
||||
jsr cbm.IOINIT
|
||||
@@ -367,8 +380,6 @@ asmsub init_system() {
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
@@ -376,7 +387,10 @@ asmsub init_system() {
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
rts ; no phase 2 steps on the C128
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,7 +36,7 @@ romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101
|
||||
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||
; there is also floats.FREAD{S,U}24AXY that read (un)signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
||||
@@ -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
|
||||
@@ -108,7 +104,6 @@ asmsub FREADUS32 () clobbers(A,X,Y) {
|
||||
|
||||
asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y) {
|
||||
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
|
||||
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
|
||||
%asm {{
|
||||
sty $62
|
||||
stx $63
|
||||
@@ -123,6 +118,39 @@ asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FREADU24AXY(ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A, X, Y) -> float @FAC1 {
|
||||
; ---- fac1 = unsigned int24 (A/X/Y contain lo/mid/hi bytes)
|
||||
%asm{{
|
||||
FAC = $61
|
||||
sty FAC+1
|
||||
stx FAC+2
|
||||
sta FAC+3
|
||||
|
||||
cpy #$00
|
||||
bne +
|
||||
cpx #$00
|
||||
bne +
|
||||
cmp #$00
|
||||
beq ++
|
||||
|
||||
+ ldx #$98
|
||||
bit FAC+1
|
||||
bmi +
|
||||
|
||||
- dex
|
||||
asl FAC+3
|
||||
rol FAC+2
|
||||
rol FAC+1
|
||||
bpl -
|
||||
|
||||
+ stx FAC
|
||||
lda #$00
|
||||
sta FAC+4
|
||||
sta FAC+5
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
@@ -181,6 +209,13 @@ asmsub normalize(float value @FAC1) -> float @ FAC1 {
|
||||
}}
|
||||
}
|
||||
|
||||
; get the jiffy clock as a float
|
||||
asmsub time() -> float @ FAC1 {
|
||||
%asm {{
|
||||
jsr cbm.RDTIM
|
||||
jmp floats.FREADU24AXY
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
@@ -6,108 +6,6 @@ func_sign_f_into_A .proc
|
||||
jmp SIGN
|
||||
.pend
|
||||
|
||||
func_swap_f .proc
|
||||
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||
ldy #4
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_reverse_f .proc
|
||||
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
_left_index = P8ZP_SCRATCH_W2
|
||||
_right_index = P8ZP_SCRATCH_W2+1
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
pha
|
||||
jsr a_times_5
|
||||
sec
|
||||
sbc #5
|
||||
sta _right_index
|
||||
lda #0
|
||||
sta _left_index
|
||||
pla
|
||||
lsr a
|
||||
sta _loop_count
|
||||
_loop ; push the left indexed float on the stack
|
||||
ldy _left_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
; copy right index float to left index float
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
; pop the float off the stack into the right index float
|
||||
ldy _right_index
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
lda _right_index
|
||||
sec
|
||||
sbc #9
|
||||
sta _right_index
|
||||
dec _loop_count
|
||||
bne _loop
|
||||
rts
|
||||
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
a_times_5 .proc
|
||||
sta P8ZP_SCRATCH_B1
|
||||
@@ -118,26 +16,6 @@ a_times_5 .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_any_f_into_A .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_any_b_into_A
|
||||
.pend
|
||||
|
||||
func_all_f_into_A .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_into_A
|
||||
.pend
|
||||
|
||||
func_any_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_any_b_stack
|
||||
.pend
|
||||
|
||||
func_all_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_stack
|
||||
.pend
|
||||
|
||||
func_abs_f_into_FAC1 .proc
|
||||
jsr MOVFM
|
||||
jmp ABS
|
||||
|
||||
@@ -288,17 +288,30 @@ hline_zero2
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
; Midpoint algorithm, filled.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word decisionOver2 = (1 as word)-radius
|
||||
ubyte last_y3 = ycenter+radius
|
||||
ubyte last_y4 = ycenter-radius
|
||||
ubyte new_y3, new_y4
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||
|
||||
new_y3 = ycenter+radius
|
||||
if new_y3 != last_y3 {
|
||||
horizontal_line(xcenter-yy, last_y3, yy*2+1)
|
||||
last_y3 = new_y3
|
||||
}
|
||||
new_y4 = ycenter-radius
|
||||
if new_y4 != last_y4 {
|
||||
horizontal_line(xcenter-yy, last_y4, yy*2+1)
|
||||
last_y4 = new_y4
|
||||
}
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -307,6 +320,10 @@ hline_zero2
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
; draw the final two spans
|
||||
yy--
|
||||
horizontal_line(xcenter-yy, last_y3, yy*2+1)
|
||||
horizontal_line(xcenter-yy, last_y4, yy*2+1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -341,6 +347,13 @@ sys {
|
||||
|
||||
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
const ubyte sizeof_bool = 1
|
||||
const ubyte sizeof_byte = 1
|
||||
const ubyte sizeof_ubyte = 1
|
||||
const ubyte sizeof_word = 2
|
||||
const ubyte sizeof_uword = 2
|
||||
const ubyte sizeof_float = 5
|
||||
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
@@ -351,7 +364,6 @@ asmsub init_system() {
|
||||
; Uppercase charset is activated.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
lda #%00101111
|
||||
sta $00
|
||||
lda #%00100111
|
||||
@@ -366,8 +378,6 @@ asmsub init_system() {
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
@@ -375,7 +385,10 @@ asmsub init_system() {
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
rts ; no phase 2 steps on the C64
|
||||
cld
|
||||
clc
|
||||
clv
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {{
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
; **experimental** data compression/decompression routines, API subject to change!!
|
||||
|
||||
compression {
|
||||
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
sub encode_rle_outfunc(uword data, uword size, uword output_function, bool is_last_block) {
|
||||
; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding.
|
||||
; output_function = address of a routine that gets a byte arg in A,
|
||||
; this is the next RLE byte to write to the compressed output buffer or file.
|
||||
; is_last_block = usually true, but you can set it to false if you want to concatenate multiple
|
||||
; compressed blocks (for instance if the source data is >64Kb)
|
||||
; Worst case result storage size needed = (size + (size+126) / 127) + 1
|
||||
; This routine is not optimized for speed but for readability and ease of use.
|
||||
uword idx = 0
|
||||
uword literals_start_idx = 0
|
||||
ubyte literals_length = 0
|
||||
|
||||
asmsub call_output_function(ubyte arg @A) {
|
||||
%asm {{
|
||||
jmp (p8v_output_function)
|
||||
}}
|
||||
}
|
||||
|
||||
sub next_same_span() {
|
||||
; returns length in cx16.r1L, and the byte value in cx16.r1H
|
||||
cx16.r1H = data[idx]
|
||||
cx16.r1L = 0
|
||||
while data[idx]==cx16.r1H and cx16.r1L<128 and idx<size {
|
||||
idx++
|
||||
cx16.r1L++
|
||||
}
|
||||
}
|
||||
|
||||
sub output_literals() {
|
||||
call_output_function(literals_length-1)
|
||||
uword dataptr = data + literals_start_idx
|
||||
ubyte i
|
||||
for i in 0 to literals_length-1 {
|
||||
call_output_function(@(dataptr))
|
||||
dataptr++
|
||||
}
|
||||
literals_length = 0
|
||||
}
|
||||
|
||||
while idx<size {
|
||||
next_same_span() ; count in r1L, value in r1H
|
||||
if cx16.r1L>1 {
|
||||
; a replicate run
|
||||
if literals_length>0
|
||||
output_literals()
|
||||
call_output_function((cx16.r1L^255)+2) ; 257-cx16.r1L
|
||||
call_output_function(cx16.r1H)
|
||||
}
|
||||
else {
|
||||
; add more to the literals run
|
||||
if literals_length==128
|
||||
output_literals()
|
||||
if literals_length==0
|
||||
literals_start_idx = idx-1
|
||||
literals_length++
|
||||
}
|
||||
}
|
||||
|
||||
if literals_length>0
|
||||
output_literals()
|
||||
|
||||
if is_last_block
|
||||
call_output_function(128)
|
||||
}
|
||||
|
||||
sub encode_rle(uword data, uword size, uword target, bool is_last_block) -> uword {
|
||||
; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding.
|
||||
; Returns the size of the compressed RLE data. Worst case result storage size needed = (size + (size+126) / 127) + 1.
|
||||
; is_last_block = usually true, but you can set it to false if you want to concatenate multiple
|
||||
; compressed blocks (for instance if the source data is >64Kb)
|
||||
; This routine is not optimized for speed but for readability and ease of use.
|
||||
uword idx = 0
|
||||
uword literals_start_idx = 0
|
||||
ubyte literals_length = 0
|
||||
uword orig_target = target
|
||||
|
||||
sub next_same_span() {
|
||||
; returns length in cx16.r1L, and the byte value in cx16.r1H
|
||||
cx16.r1H = data[idx]
|
||||
cx16.r1L = 0
|
||||
while data[idx]==cx16.r1H and cx16.r1L<128 and idx<size {
|
||||
idx++
|
||||
cx16.r1L++
|
||||
}
|
||||
}
|
||||
|
||||
sub output_literals() {
|
||||
@(target) = literals_length-1
|
||||
target++
|
||||
uword dataptr = data + literals_start_idx
|
||||
ubyte i
|
||||
for i in 0 to literals_length-1 {
|
||||
@(target) = @(dataptr)
|
||||
target++
|
||||
dataptr++
|
||||
}
|
||||
literals_length = 0
|
||||
}
|
||||
|
||||
while idx<size {
|
||||
next_same_span() ; count in r1L, value in r1H
|
||||
if cx16.r1L>1 {
|
||||
; a replicate run
|
||||
if literals_length>0
|
||||
output_literals()
|
||||
@(target) = (cx16.r1L^255)+2 ; 257-cx16.r1L
|
||||
target++
|
||||
@(target) = cx16.r1H
|
||||
target++
|
||||
}
|
||||
else {
|
||||
; add more to the literals run
|
||||
if literals_length==128
|
||||
output_literals()
|
||||
if literals_length==0
|
||||
literals_start_idx = idx-1
|
||||
literals_length++
|
||||
}
|
||||
}
|
||||
|
||||
if literals_length>0
|
||||
output_literals()
|
||||
|
||||
if is_last_block {
|
||||
@(target) = 128
|
||||
target ++
|
||||
}
|
||||
|
||||
return target-orig_target
|
||||
}
|
||||
|
||||
asmsub decode_rle_srcfunc(uword source_function @AY, uword target @R0, uword maxsize @R1) clobbers(X) -> uword @AY {
|
||||
; -- decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
|
||||
; instead of a source buffer, you provide a callback function that must return the next byte to compress in A.
|
||||
; Stops decompressing when the maxsize has been reached.
|
||||
%asm {{
|
||||
sta _cb_mod1+1
|
||||
sty _cb_mod1+2
|
||||
sta _cb_mod2+1
|
||||
sty _cb_mod2+2
|
||||
sta _cb_mod3+1
|
||||
sty _cb_mod3+2
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
sta P8ZP_SCRATCH_W2 ; target ptr
|
||||
sta _orig_target
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
sty _orig_target+1
|
||||
lda cx16.r0L
|
||||
clc
|
||||
adc cx16.r1L
|
||||
sta cx16.r1L
|
||||
lda cx16.r0H
|
||||
adc cx16.r1H
|
||||
sta cx16.r1H ; decompression limit
|
||||
|
||||
_loop
|
||||
; while target (W2) < limit (r1)
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
cmp cx16.r1L
|
||||
tya
|
||||
sbc cx16.r1H
|
||||
bcs _end
|
||||
|
||||
_cb_mod1 jsr $ffff ; modified
|
||||
bpl _literals
|
||||
cmp #128
|
||||
beq _end
|
||||
|
||||
; replicate next byte -n+1 times
|
||||
eor #255
|
||||
clc
|
||||
adc #2
|
||||
sta P8ZP_SCRATCH_REG
|
||||
_cb_mod2 jsr $ffff ; modified
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
; add A to target
|
||||
lda P8ZP_SCRATCH_REG
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #0
|
||||
adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jmp _loop
|
||||
_literals
|
||||
; copy the next n+1 bytes
|
||||
pha
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_REG
|
||||
_cb_mod3 jsr $ffff ; modified
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
inc P8ZP_SCRATCH_REG
|
||||
dec P8ZP_SCRATCH_B1
|
||||
bpl _cb_mod3
|
||||
; add N+1 to target
|
||||
pla
|
||||
sec
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #0
|
||||
adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jmp _loop
|
||||
|
||||
_orig_target .word 0
|
||||
_end
|
||||
; return w2-orig_target, the size of the decompressed data
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
sec
|
||||
sbc _orig_target
|
||||
tax
|
||||
tya
|
||||
sbc _orig_target+1
|
||||
tay
|
||||
txa
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub decode_rle(uword compressed @AY, uword target @R0, uword maxsize @R1) clobbers(X) -> uword @AY {
|
||||
; -- decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
|
||||
; Stops decompressing when the maxsize has been reached.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1 ; compressed data ptr
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
sta P8ZP_SCRATCH_W2 ; target ptr
|
||||
sta _orig_target
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
sty _orig_target+1
|
||||
lda cx16.r0L
|
||||
clc
|
||||
adc cx16.r1L
|
||||
sta cx16.r1L
|
||||
lda cx16.r0H
|
||||
adc cx16.r1H
|
||||
sta cx16.r1H ; decompression limit
|
||||
|
||||
_loop ; while target (W2) < limit (r1)
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
cmp cx16.r1L
|
||||
tya
|
||||
sbc cx16.r1H
|
||||
bcs _end
|
||||
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
bpl _literals
|
||||
cmp #128
|
||||
beq _end
|
||||
|
||||
; replicate next byte -n+1 times
|
||||
eor #255
|
||||
clc
|
||||
adc #2
|
||||
pha
|
||||
tax
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy #0
|
||||
- sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
; add A to target
|
||||
pla
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #0
|
||||
adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
; increase source by 2
|
||||
clc
|
||||
lda P8ZP_SCRATCH_W1
|
||||
adc #2
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #0
|
||||
adc P8ZP_SCRATCH_W1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
jmp _loop
|
||||
_literals
|
||||
; copy the next n+1 bytes
|
||||
pha
|
||||
tax
|
||||
inc P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
dex
|
||||
bpl -
|
||||
; add N+1 to source
|
||||
pla
|
||||
tax
|
||||
sec
|
||||
adc P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #0
|
||||
adc P8ZP_SCRATCH_W1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
; add N+1 to target as well
|
||||
txa
|
||||
sec
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #0
|
||||
adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jmp _loop
|
||||
|
||||
_orig_target .word 0
|
||||
_end
|
||||
; return w2-orig_target, the size of the decompressed data
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
sec
|
||||
sbc _orig_target
|
||||
tax
|
||||
tya
|
||||
sbc _orig_target+1
|
||||
tay
|
||||
txa
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
; prog8 source code for the asm routine above:
|
||||
|
||||
sub decode_rle_prog8(uword @zp compressed, uword @zp target, uword maxsize) -> uword {
|
||||
cx16.r0 = target ; original target
|
||||
cx16.r1 = target+maxsize ; decompression limit
|
||||
|
||||
while target<cx16.r1 {
|
||||
cx16.r2L = @(compressed)
|
||||
if_neg {
|
||||
if cx16.r2L==128
|
||||
break
|
||||
; replicate the next byte -n+1 times
|
||||
compressed++
|
||||
cx16.r3L = @(compressed)
|
||||
repeat 2+(cx16.r2L^255) {
|
||||
@(target) = cx16.r3L
|
||||
target++
|
||||
}
|
||||
compressed++
|
||||
} else {
|
||||
; copy the next n+1 bytes
|
||||
compressed++
|
||||
repeat cx16.r2L+1 {
|
||||
@(target) = @(compressed)
|
||||
compressed++
|
||||
target++
|
||||
}
|
||||
}
|
||||
}
|
||||
return target-cx16.r0
|
||||
}
|
||||
|
||||
sub decode_rle_callback_prog8(uword producer_callback, uword @zp target, uword maxsize) -> uword {
|
||||
cx16.r0 = target ; original target
|
||||
cx16.r1 = target+maxsize ; decompression limit
|
||||
|
||||
while target<cx16.r1 {
|
||||
cx16.r2L = lsb(call(producer_callback))
|
||||
if_neg {
|
||||
if cx16.r2L==128
|
||||
break
|
||||
; replicate the next byte -n+1 times
|
||||
cx16.r3L = lsb(call(producer_callback))
|
||||
repeat 2+(cx16.r2L^255) {
|
||||
@(target) = cx16.r3L
|
||||
target++
|
||||
}
|
||||
} else {
|
||||
; copy the next n+1 bytes
|
||||
repeat cx16.r2L+1 {
|
||||
@(target) = lsb(call(producer_callback))
|
||||
target++
|
||||
}
|
||||
}
|
||||
}
|
||||
return target-cx16.r0
|
||||
}
|
||||
***/
|
||||
|
||||
}
|
||||
+95
-105
@@ -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
|
||||
|
||||
Loaded 100 of 308 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user