mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10ddd5b127 | ||
|
|
f46e131f18 | ||
|
|
feb5c8be95 | ||
|
|
edf12bec71 | ||
|
|
ff1fc28287 | ||
|
|
314398ba4c | ||
|
|
840331347b | ||
|
|
6181b12ab8 | ||
|
|
68da661edc | ||
|
|
88cbb6913d | ||
|
|
7a26646e1b | ||
|
|
92eb3b0bf6 | ||
|
|
fb63434eee | ||
|
|
97f90d9684 | ||
|
|
f91786367f | ||
|
|
6a57337a68 | ||
|
|
211e2bb37a | ||
|
|
d2d08bf143 | ||
|
|
8acb37b6c2 | ||
|
|
81b3d2db4f | ||
|
|
9633c0b07a | ||
|
|
1dfa8ee7d8 | ||
|
|
1163543a98 | ||
|
|
bdb7de34be | ||
|
|
9500fc11ac | ||
|
|
65daf29acd | ||
|
|
298b25cf7d | ||
|
|
41f4e22a17 | ||
|
|
288c57c144 | ||
|
|
7ff8923569 | ||
|
|
b41779bd02 | ||
|
|
beea6bc794 | ||
|
|
fee58e98c5 | ||
|
|
c51c1da618 | ||
|
|
ea2812f50f | ||
|
|
3ec05709d5 | ||
|
|
4bdac7404a | ||
|
|
cc41218d37 | ||
|
|
4b336b1853 | ||
|
|
e1c77ce236 | ||
|
|
064d412ec8 | ||
|
|
7fff4f249d | ||
|
|
7a3745f642 | ||
|
|
f8658f6afa | ||
|
|
223b725a10 | ||
|
|
25aad8d7be | ||
|
|
b2c9b7635d | ||
|
|
24d13dd120 | ||
|
|
965340ff90 | ||
|
|
8e36fe6bef | ||
|
|
2eb41a8caf | ||
|
|
fb989ae62f | ||
|
|
7901ec2a64 | ||
|
|
f675dbc726 | ||
|
|
2ad4fdbbb9 | ||
|
|
97cb0cbd08 | ||
|
|
4ca0805de1 | ||
|
|
4b358abbb7 | ||
|
|
dc82a0fc16 | ||
|
|
435d6f6f3f | ||
|
|
ef92451d1a | ||
|
|
06184bdcb1 | ||
|
|
af98d01053 | ||
|
|
bb1cda0916 | ||
|
|
a6d0ea347c | ||
|
|
0fcd57192b | ||
|
|
a6ffa5738b | ||
|
|
c75bd97537 | ||
|
|
eea09f4de5 | ||
|
|
5656ec11d3 | ||
|
|
eb53e44cb0 | ||
|
|
69f3106062 | ||
|
|
8ab99f6129 |
No files matched your search
+9
-9
@@ -1,21 +1,21 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.2.3" />
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.3.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.2.3/kotest-assertions-core-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.10/kotlin-stdlib-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.3.2/kotest-assertions-core-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.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.6.10/kotlin-stdlib-jdk7-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.2.3/kotest-assertions-shared-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.3.2/kotest-assertions-shared-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.1/kotlinx-coroutines-jdk8-1.6.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.1/kotlinx-coroutines-core-jvm-1.6.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.2.3/kotest-common-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.2.3/kotest-assertions-api-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.3.2/kotest-common-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.3.2/kotest-assertions-api-jvm-5.3.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
+9
-9
@@ -1,20 +1,20 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.property.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-property-jvm:5.2.3" />
|
||||
<properties maven-id="io.kotest:kotest-property-jvm:5.3.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/5.2.3/kotest-property-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/5.3.2/kotest-property-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/curious-odd-man/rgxgen/1.3/rgxgen-1.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.10/kotlin-stdlib-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.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.6.10/kotlin-stdlib-jdk7-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.2.3/kotest-common-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.2.3/kotest-assertions-shared-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.2.3/kotest-assertions-api-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.3.2/kotest-common-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.3.2/kotest-assertions-shared-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.3.2/kotest-assertions-api-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.1/kotlinx-coroutines-jdk8-1.6.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.1/kotlinx-coroutines-core-jvm-1.6.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
|
||||
</CLASSES>
|
||||
|
||||
+25
-25
@@ -1,38 +1,38 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.2.3" />
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.3.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.2.3/kotest-runner-junit5-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.2.3/kotest-framework-api-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.2.3/kotest-assertions-shared-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.3.2/kotest-runner-junit5-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.3.2/kotest-framework-api-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.3.2/kotest-assertions-shared-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.6.1/kotlinx-coroutines-test-jvm-1.6.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.2.3/kotest-common-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.2.3/kotest-framework-engine-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.138/classgraph-4.8.138.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.3.2/kotest-common-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.3.2/kotest-framework-engine-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.146/classgraph-4.8.146.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.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.6.1/kotlinx-coroutines-debug-1.6.1.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$/io/kotest/kotest-framework-discovery-jvm/5.2.3/kotest-framework-discovery-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.2.3/kotest-assertions-core-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.3.2/kotest-framework-discovery-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.3.2/kotest-assertions-core-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.1/kotlinx-coroutines-jdk8-1.6.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.2.3/kotest-assertions-api-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.2.3/kotest-extensions-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.3.2/kotest-assertions-api-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.3.2/kotest-extensions-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.11.0/commons-io-2.11.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.12.2/mockk-1.12.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.12.2/mockk-dsl-jvm-1.12.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.12.2/mockk-dsl-1.12.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.12.2/mockk-common-1.12.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.12.2/mockk-agent-jvm-1.12.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.12.2/mockk-agent-api-1.12.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.12.2/mockk-agent-common-1.12.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.12.3/mockk-1.12.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.12.3/mockk-common-1.12.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.12.3/mockk-dsl-1.12.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.12.3/mockk-dsl-jvm-1.12.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.12.3/mockk-agent-jvm-1.12.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.12.3/mockk-agent-api-1.12.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.12.3/mockk-agent-common-1.12.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.1/objenesis-3.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.12.5/byte-buddy-1.12.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.12.5/byte-buddy-agent-1.12.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.2.3/kotest-framework-concurrency-jvm-5.2.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.12.6/byte-buddy-1.12.6.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.12.6/byte-buddy-agent-1.12.6.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.3.2/kotest-framework-concurrency-jvm-5.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.1/kotlinx-coroutines-core-jvm-1.6.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
|
||||
@@ -41,11 +41,11 @@
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.7.2/junit-platform-suite-api-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.7.2/junit-platform-launcher-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.10/kotlin-stdlib-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.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.6.10/kotlin-stdlib-jdk7-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
@@ -40,6 +40,20 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
||||
vars
|
||||
}
|
||||
|
||||
val allMemMappedVariables: Collection<StMemVar> by lazy {
|
||||
val vars = mutableListOf<StMemVar>()
|
||||
fun collect(node: StNode) {
|
||||
for(child in node.children) {
|
||||
if(child.value.type== StNodeType.MEMVAR)
|
||||
vars.add(child.value as StMemVar)
|
||||
else
|
||||
collect(child.value)
|
||||
}
|
||||
}
|
||||
collect(this)
|
||||
vars
|
||||
}
|
||||
|
||||
override fun lookup(scopedName: List<String>) = flat[scopedName]
|
||||
}
|
||||
|
||||
@@ -186,7 +200,11 @@ class StConstant(name: String, val dt: DataType, val value: Double, position: Po
|
||||
}
|
||||
|
||||
|
||||
class StMemVar(name: String, val dt: DataType, val address: UInt, position: Position) :
|
||||
class StMemVar(name: String,
|
||||
val dt: DataType,
|
||||
val address: UInt,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
position: Position) :
|
||||
StNode(name, StNodeType.MEMVAR, position) {
|
||||
override fun printProperties() {
|
||||
print("$name dt=$dt address=${address.toHex()}")
|
||||
|
||||
@@ -8,6 +8,12 @@ import kotlin.math.round
|
||||
|
||||
|
||||
sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) {
|
||||
|
||||
init {
|
||||
if(type==DataType.BOOL)
|
||||
throw IllegalArgumentException("bool should have become ubyte @$position")
|
||||
}
|
||||
|
||||
override fun printProperties() {
|
||||
print(type)
|
||||
}
|
||||
@@ -73,6 +79,7 @@ class PtBuiltinFunctionCall(val name: String,
|
||||
|
||||
|
||||
class PtBinaryExpression(val operator: String, type: DataType, position: Position): PtExpression(type, position) {
|
||||
// note: "and", "or", "xor" do not occur anymore as operators. They've been replaced int the ast by their bitwise versions &, |, ^.
|
||||
val left: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val right: PtExpression
|
||||
@@ -126,10 +133,12 @@ class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position)
|
||||
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
|
||||
|
||||
init {
|
||||
if(type==DataType.BOOL)
|
||||
throw IllegalArgumentException("bool should have become ubyte @$position")
|
||||
if(type!=DataType.FLOAT) {
|
||||
val rounded = round(number)
|
||||
if (rounded != number)
|
||||
throw IllegalArgumentException("refused rounding of float to avoid loss of precision")
|
||||
throw IllegalArgumentException("refused rounding of float to avoid loss of precision @$position")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +162,12 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr
|
||||
val value: PtExpression
|
||||
get() = children.single() as PtExpression
|
||||
|
||||
init {
|
||||
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
|
||||
if(operator !in setOf("+", "-", "~"))
|
||||
throw IllegalArgumentException("invalid prefix operator: $operator")
|
||||
}
|
||||
|
||||
override fun printProperties() {
|
||||
print(operator)
|
||||
}
|
||||
|
||||
@@ -20,5 +20,7 @@ class CompilationOptions(val output: OutputType,
|
||||
var asmQuiet: Boolean = false,
|
||||
var asmListfile: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var outputDir: Path = Path("")
|
||||
var evalStackBaseAddress: UInt? = null,
|
||||
var outputDir: Path = Path(""),
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
)
|
||||
@@ -6,12 +6,14 @@ enum class DataType {
|
||||
UWORD, // pass by value
|
||||
WORD, // pass by value
|
||||
FLOAT, // pass by value
|
||||
BOOL, // pass by value
|
||||
STR, // pass by reference
|
||||
ARRAY_UB, // pass by reference
|
||||
ARRAY_B, // pass by reference
|
||||
ARRAY_UW, // pass by reference
|
||||
ARRAY_W, // pass by reference
|
||||
ARRAY_F, // pass by reference
|
||||
ARRAY_BOOL, // pass by reference
|
||||
UNDEFINED;
|
||||
|
||||
/**
|
||||
@@ -19,11 +21,12 @@ enum class DataType {
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(this) {
|
||||
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT)
|
||||
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, FLOAT)
|
||||
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT, BOOL)
|
||||
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT)
|
||||
UWORD -> targetType.oneOf(UWORD, FLOAT)
|
||||
WORD -> targetType.oneOf(WORD, FLOAT)
|
||||
FLOAT -> targetType == FLOAT
|
||||
FLOAT -> targetType.oneOf(FLOAT)
|
||||
STR -> targetType.oneOf(STR, UWORD)
|
||||
in ArrayDatatypes -> targetType == this
|
||||
else -> false
|
||||
@@ -109,18 +112,20 @@ enum class BranchCondition {
|
||||
}
|
||||
|
||||
|
||||
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE)
|
||||
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
|
||||
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
|
||||
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.BOOL)
|
||||
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT, DataType.BOOL)
|
||||
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F, DataType.ARRAY_BOOL)
|
||||
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||
val IterableDatatypes = arrayOf(
|
||||
DataType.STR,
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W,
|
||||
DataType.ARRAY_F
|
||||
DataType.ARRAY_F, DataType.ARRAY_BOOL
|
||||
)
|
||||
val PassByValueDatatypes = NumericDatatypes
|
||||
val PassByReferenceDatatypes = IterableDatatypes
|
||||
@@ -130,14 +135,16 @@ val ArrayToElementTypes = mapOf(
|
||||
DataType.ARRAY_UB to DataType.UBYTE,
|
||||
DataType.ARRAY_W to DataType.WORD,
|
||||
DataType.ARRAY_UW to DataType.UWORD,
|
||||
DataType.ARRAY_F to DataType.FLOAT
|
||||
DataType.ARRAY_F to DataType.FLOAT,
|
||||
DataType.ARRAY_BOOL to DataType.BOOL
|
||||
)
|
||||
val ElementToArrayTypes = mapOf(
|
||||
DataType.BYTE to DataType.ARRAY_B,
|
||||
DataType.UBYTE to DataType.ARRAY_UB,
|
||||
DataType.WORD to DataType.ARRAY_W,
|
||||
DataType.UWORD to DataType.ARRAY_UW,
|
||||
DataType.FLOAT to DataType.ARRAY_F
|
||||
DataType.FLOAT to DataType.ARRAY_F,
|
||||
DataType.BOOL to DataType.ARRAY_BOOL
|
||||
)
|
||||
val Cx16VirtualRegisters = arrayOf(
|
||||
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
||||
|
||||
@@ -18,8 +18,8 @@ interface IMachineDefinition {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
val ESTACK_LO: UInt
|
||||
val ESTACK_HI: UInt
|
||||
var ESTACK_LO: UInt
|
||||
var ESTACK_HI: UInt
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
|
||||
val opcodeNames: Set<String>
|
||||
@@ -32,4 +32,9 @@ interface IMachineDefinition {
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
fun isIOAddress(address: UInt): Boolean
|
||||
fun overrideEvalStack(evalStackBaseAddress: UInt) {
|
||||
require(evalStackBaseAddress and 255u == 0u)
|
||||
ESTACK_LO = evalStackBaseAddress
|
||||
ESTACK_HI = evalStackBaseAddress + 256u
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package prog8.code.core
|
||||
|
||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=")
|
||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not")
|
||||
val BitwiseOperators = setOf("&", "|", "^")
|
||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%")
|
||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
|
||||
|
||||
fun invertedComparisonOperator(operator: String) =
|
||||
when (operator) {
|
||||
|
||||
@@ -15,8 +15,8 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x2000u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive // TODO
|
||||
override val ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive // TODO
|
||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
|
||||
override val ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
|
||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
||||
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
||||
override val ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
||||
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
||||
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = 4 // 32-bits floating point
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||
|
||||
override val ESTACK_LO = 0u // not actually used
|
||||
override val ESTACK_HI = 0u // not actually used
|
||||
override var ESTACK_LO = 0u // not actually used
|
||||
override var ESTACK_HI = 0u // not actually used
|
||||
|
||||
override lateinit var zeropage: Zeropage // not actually used
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeLines
|
||||
|
||||
|
||||
internal const val generatedLabelPrefix = "prog8_label_"
|
||||
internal const val subroutineFloatEvalResultVar1 = "prog8_float_eval_result1"
|
||||
internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
|
||||
|
||||
@@ -73,13 +72,6 @@ class AsmGen(internal val program: Program,
|
||||
|
||||
internal fun isTargetCpu(cpu: CpuType) = options.compTarget.machine.cpu == cpu
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
|
||||
internal fun makeLabel(postfix: String): String {
|
||||
generatedLabelSequenceNumber++
|
||||
return "${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
|
||||
}
|
||||
|
||||
internal fun outputSourceLine(node: Node) {
|
||||
out(" ;\tsrc line: ${node.position.file}:${node.position.line}")
|
||||
}
|
||||
@@ -543,7 +535,7 @@ class AsmGen(internal val program: Program,
|
||||
if(jump is Jump) {
|
||||
translateCompareAndJumpIfTrue(booleanCondition, jump)
|
||||
} else {
|
||||
val endLabel = makeLabel("if_end")
|
||||
val endLabel = program.makeLabel("if_end")
|
||||
translateCompareAndJumpIfFalse(booleanCondition, endLabel)
|
||||
translate(stmt.truepart)
|
||||
out(endLabel)
|
||||
@@ -551,8 +543,8 @@ class AsmGen(internal val program: Program,
|
||||
}
|
||||
else {
|
||||
// both true and else parts
|
||||
val elseLabel = makeLabel("if_else")
|
||||
val endLabel = makeLabel("if_end")
|
||||
val elseLabel = program.makeLabel("if_else")
|
||||
val endLabel = program.makeLabel("if_end")
|
||||
translateCompareAndJumpIfFalse(booleanCondition, elseLabel)
|
||||
translate(stmt.truepart)
|
||||
jmp(endLabel)
|
||||
@@ -568,7 +560,7 @@ class AsmGen(internal val program: Program,
|
||||
}
|
||||
|
||||
private fun translate(stmt: RepeatLoop) {
|
||||
val endLabel = makeLabel("repeatend")
|
||||
val endLabel = program.makeLabel("repeatend")
|
||||
loopEndLabels.push(endLabel)
|
||||
|
||||
when (stmt.iterations) {
|
||||
@@ -621,7 +613,7 @@ class AsmGen(internal val program: Program,
|
||||
|
||||
private fun repeatWordCount(count: Int, stmt: RepeatLoop) {
|
||||
require(count in 257..65535)
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val repeatLabel = program.makeLabel("repeat")
|
||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
|
||||
out("""
|
||||
@@ -662,7 +654,7 @@ $repeatLabel""")
|
||||
private fun repeatWordCountInAY(endLabel: String, stmt: RepeatLoop) {
|
||||
// note: A/Y must have been loaded with the number of iterations!
|
||||
// no need to explicitly test for 0 iterations as this is done in the countdown logic below
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val repeatLabel = program.makeLabel("repeat")
|
||||
val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt)
|
||||
out("""
|
||||
sta $counterVar
|
||||
@@ -683,7 +675,7 @@ $repeatLabel lda $counterVar
|
||||
|
||||
private fun repeatByteCount(count: Int, stmt: RepeatLoop) {
|
||||
require(count in 2..256)
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val repeatLabel = program.makeLabel("repeat")
|
||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
|
||||
out(" lda #${count and 255} | sta $counterVar")
|
||||
@@ -701,7 +693,7 @@ $repeatLabel lda $counterVar
|
||||
|
||||
private fun repeatCountInY(stmt: RepeatLoop, endLabel: String) {
|
||||
// note: Y must just have been loaded with the (variable) number of loops to be performed!
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val repeatLabel = program.makeLabel("repeat")
|
||||
if(isTargetCpu(CpuType.CPU65c02)) {
|
||||
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
|
||||
out(" beq $endLabel | sty $counterVar")
|
||||
@@ -738,7 +730,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
val counterVar = makeLabel("counter")
|
||||
val counterVar = program.makeLabel("counter")
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> {
|
||||
val result = zeropage.allocate(listOf(counterVar), dt, null, stmt.position, errors)
|
||||
@@ -753,7 +745,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun translate(stmt: When) {
|
||||
val endLabel = makeLabel("choice_end")
|
||||
val endLabel = program.makeLabel("choice_end")
|
||||
val choiceBlocks = mutableListOf<Pair<String, AnonymousScope>>()
|
||||
val conditionDt = stmt.condition.inferType(program)
|
||||
if(!conditionDt.isKnown)
|
||||
@@ -764,7 +756,7 @@ $repeatLabel lda $counterVar
|
||||
assignExpressionToRegister(stmt.condition, RegisterOrPair.AY)
|
||||
|
||||
for(choice in stmt.choices) {
|
||||
val choiceLabel = makeLabel("choice")
|
||||
val choiceLabel = program.makeLabel("choice")
|
||||
if(choice.values==null) {
|
||||
// the else choice
|
||||
translate(choice.statements)
|
||||
@@ -828,14 +820,14 @@ $repeatLabel lda $counterVar
|
||||
} else {
|
||||
if(stmt.elsepart.isEmpty()) {
|
||||
val instruction = branchInstruction(stmt.condition, true)
|
||||
val elseLabel = makeLabel("branch_else")
|
||||
val elseLabel = program.makeLabel("branch_else")
|
||||
out(" $instruction $elseLabel")
|
||||
translate(stmt.truepart)
|
||||
out(elseLabel)
|
||||
} else {
|
||||
val instruction = branchInstruction(stmt.condition, true)
|
||||
val elseLabel = makeLabel("branch_else")
|
||||
val endLabel = makeLabel("branch_end")
|
||||
val elseLabel = program.makeLabel("branch_else")
|
||||
val endLabel = program.makeLabel("branch_end")
|
||||
out(" $instruction $elseLabel")
|
||||
translate(stmt.truepart)
|
||||
jmp(endLabel)
|
||||
@@ -1173,14 +1165,14 @@ $repeatLabel lda $counterVar
|
||||
return testVariableZeroAndJump(left, dt, operator, jumpIfFalseLabel)
|
||||
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> {
|
||||
DataType.BOOL, DataType.UBYTE, DataType.UWORD -> {
|
||||
if(operator=="<") {
|
||||
out(" jmp $jumpIfFalseLabel")
|
||||
return
|
||||
} else if(operator==">=") {
|
||||
return
|
||||
}
|
||||
if(dt==DataType.UBYTE) {
|
||||
if(dt==DataType.UBYTE || dt==DataType.BOOL) {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, false)
|
||||
if (left is IFunctionCall && !left.isSimple)
|
||||
out(" cmp #0")
|
||||
@@ -1260,7 +1252,7 @@ $repeatLabel lda $counterVar
|
||||
// optimized code if the expression is just an identifier (variable)
|
||||
val varname = asmVariableName(variable)
|
||||
when(dt) {
|
||||
DataType.UBYTE -> when(operator) {
|
||||
DataType.UBYTE, DataType.BOOL -> when(operator) {
|
||||
"==" -> out(" lda $varname | bne $jumpIfFalseLabel")
|
||||
"!=" -> out(" lda $varname | beq $jumpIfFalseLabel")
|
||||
">" -> out(" lda $varname | beq $jumpIfFalseLabel")
|
||||
@@ -2882,7 +2874,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, target.datatype, scope, variableAsmName = asmVariableName(target.name))
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, target.datatype, scope, variableAsmName = asmVariableName(target.scopedName))
|
||||
if (dt in ByteDatatypes) {
|
||||
out(" pla")
|
||||
assignRegister(RegisterOrPair.A, tgt)
|
||||
|
||||
@@ -3,6 +3,7 @@ package prog8.codegen.cpu6502
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.mapError
|
||||
import prog8.ast.generatedLabelPrefix
|
||||
import prog8.code.core.*
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
@@ -44,7 +44,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"boolean" -> funcBoolean(fcall, resultToStack, resultRegister, sscope)
|
||||
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
|
||||
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"rol" -> funcRol(fcall)
|
||||
@@ -698,28 +697,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcBoolean(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
when (val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
in ByteDatatypes -> {
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.A, dt==DataType.BYTE)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY, dt==DataType.WORD)
|
||||
asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.FAC1, true)
|
||||
asmgen.out(" jsr floats.SIGN")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
|
||||
if(resultToStack)
|
||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
else if(resultRegister!=null)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, scope, program, asmgen), CpuRegister.A)
|
||||
}
|
||||
|
||||
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
when(func.name) {
|
||||
"rnd" -> {
|
||||
|
||||
@@ -136,7 +136,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
private fun translateExpression(typecast: TypecastExpression) {
|
||||
translateExpressionInternal(typecast.expression)
|
||||
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
DataType.UBYTE, DataType.BOOL -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
@@ -532,8 +532,8 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
in ComparisonOperators -> {
|
||||
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if(rightVal==0)
|
||||
val rightVal = expr.right.constValue(program)?.number
|
||||
if(rightVal==0.0)
|
||||
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
|
||||
}
|
||||
}
|
||||
@@ -560,6 +560,42 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) {
|
||||
if(expr.isSimple) {
|
||||
if(operator=="!=") {
|
||||
when (dt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.A, dt == DataType.BYTE)
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
return
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY, dt == DataType.WORD)
|
||||
asmgen.out("""
|
||||
sty P8ZP_SCRATCH_B1
|
||||
ora P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
return
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.FAC1, true)
|
||||
asmgen.out("""
|
||||
jsr floats.SIGN
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
/* operator == is not worth it to special case, the code is mostly larger */
|
||||
}
|
||||
translateExpressionInternal(expr)
|
||||
when(operator) {
|
||||
"==" -> {
|
||||
@@ -664,22 +700,6 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
"not" -> {
|
||||
when(type) {
|
||||
// if reg==0 ->
|
||||
/*
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
*/
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
|
||||
}
|
||||
}
|
||||
@@ -784,9 +804,6 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
|
||||
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
|
||||
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
|
||||
"and" -> asmgen.out(" jsr prog8_lib.and_b")
|
||||
"or" -> asmgen.out(" jsr prog8_lib.or_b")
|
||||
"xor" -> asmgen.out(" jsr prog8_lib.xor_b")
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
@@ -818,9 +835,6 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
"&" -> asmgen.out(" jsr prog8_lib.bitand_w")
|
||||
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
|
||||
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
|
||||
"and" -> asmgen.out(" jsr prog8_lib.and_w")
|
||||
"or" -> asmgen.out(" jsr prog8_lib.or_w")
|
||||
"xor" -> asmgen.out(" jsr prog8_lib.xor_w")
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
@@ -837,7 +851,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
">=" -> asmgen.out(" jsr floats.greatereq_f")
|
||||
"==" -> asmgen.out(" jsr floats.equal_f")
|
||||
"!=" -> asmgen.out(" jsr floats.notequal_f")
|
||||
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
|
||||
"%", "<<", ">>", "&", "^", "|" -> throw AssemblyError("requires integer datatype")
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
}
|
||||
|
||||
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
val modifiedLabel = program.makeLabel("for_modified")
|
||||
val modifiedLabel2 = program.makeLabel("for_modifiedb")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val stepsize=range.step.constValue(program)!!.number.toInt()
|
||||
|
||||
@@ -238,8 +238,8 @@ $endLabel""")
|
||||
}
|
||||
|
||||
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val iterableName = asmgen.asmVariableName(ident)
|
||||
val decl = ident.targetVarDecl(program)!!
|
||||
@@ -263,7 +263,7 @@ $endLabel""")
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
val length = decl.arraysize!!.constIndex()!!
|
||||
val indexVar = asmgen.makeLabel("for_index")
|
||||
val indexVar = program.makeLabel("for_index")
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
$loopLabel sty $indexVar
|
||||
@@ -299,7 +299,7 @@ $loopLabel sty $indexVar
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
val length = decl.arraysize!!.constIndex()!! * 2
|
||||
val indexVar = asmgen.makeLabel("for_index")
|
||||
val indexVar = program.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.loopVar)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
@@ -359,8 +359,8 @@ $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")
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
when(iterableDt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
@@ -471,8 +471,8 @@ $loopLabel""")
|
||||
}
|
||||
|
||||
private fun translateForSimpleByteRangeAsc(stmt: ForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
asmgen.out("""
|
||||
@@ -497,8 +497,8 @@ $endLabel""")
|
||||
}
|
||||
|
||||
private fun translateForSimpleByteRangeDesc(stmt: ForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
asmgen.out("""
|
||||
@@ -534,8 +534,8 @@ $endLabel""")
|
||||
}
|
||||
|
||||
private fun translateForSimpleWordRangeAsc(stmt: ForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
asmgen.out("""
|
||||
@@ -561,8 +561,8 @@ $loopLabel""")
|
||||
}
|
||||
|
||||
private fun translateForSimpleWordRangeDesc(stmt: ForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
asmgen.out("""
|
||||
|
||||
@@ -3,7 +3,10 @@ package prog8.codegen.cpu6502
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
||||
@@ -156,7 +159,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
argOrder.forEach {
|
||||
val param = callee.parameters[it]
|
||||
val targetVar = callee.searchAsmParameter(param.name)!!
|
||||
val targetVar = callee.searchParameter(param.name)!!
|
||||
asmgen.popCpuStack(param.type, targetVar, (call as Node).definingSubroutine)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,17 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
|
||||
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
|
||||
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
|
||||
asmgen.out(".weak") // hack to allow user to override the following two with command line redefinition:
|
||||
asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
|
||||
asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
|
||||
asmgen.out(".endweak")
|
||||
|
||||
if(options.symbolDefs.isNotEmpty()) {
|
||||
asmgen.out("; -- user supplied symbols on the command line")
|
||||
for((name, value) in options.symbolDefs) {
|
||||
asmgen.out("$name = $value")
|
||||
}
|
||||
}
|
||||
|
||||
when(options.output) {
|
||||
OutputType.RAW -> {
|
||||
@@ -127,11 +136,8 @@ internal class ProgramAndVarsGen(
|
||||
"cx16" -> {
|
||||
if(options.floats)
|
||||
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
|
||||
asmgen.out(" jsr main.start | lda #4 | sta $01")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
||||
else
|
||||
asmgen.out(" rts")
|
||||
asmgen.out(" jsr main.start")
|
||||
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out(" jsr main.start | lda #31 | sta $01")
|
||||
|
||||
@@ -2,7 +2,6 @@ package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.getTempRegisterName
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.AsmGen
|
||||
@@ -283,7 +282,6 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
"+" -> {}
|
||||
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
|
||||
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
|
||||
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
}
|
||||
@@ -305,9 +303,19 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
|
||||
private fun attemptAssignOptimizedBinexpr(expr: BinaryExpression, assign: AsmAssignment): Boolean {
|
||||
if(expr.operator in ComparisonOperators) {
|
||||
assignConstantByte(assign.target, 0)
|
||||
if(expr.right.constValue(program)?.number == 0.0) {
|
||||
if(expr.operator == "==" || expr.operator=="!=") {
|
||||
when(assign.target.datatype) {
|
||||
in ByteDatatypes -> if(attemptAssignToByteCompareZero(expr, assign)) return true
|
||||
else -> {
|
||||
// do nothing, this is handled by a type cast.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val origTarget = assign.target.origAstTarget
|
||||
if(origTarget!=null) {
|
||||
assignConstantByte(assign.target, 0)
|
||||
val assignTrue = AnonymousScope(mutableListOf(
|
||||
Assignment(origTarget, NumericLiteral.fromBoolean(true, assign.position), AssignmentOrigin.ASMGEN, assign.position)
|
||||
), assign.position)
|
||||
@@ -322,63 +330,6 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
if(!expr.inferType(program).isInteger)
|
||||
return false
|
||||
|
||||
// optimized code for logical expressions
|
||||
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this...
|
||||
if(expr.operator=="and") {
|
||||
val iDt = expr.left.inferType(program)
|
||||
val dt = iDt.getOrElse { throw AssemblyError("weird dt") }
|
||||
if (dt in ByteDatatypes) {
|
||||
val tmpReg = getTempRegisterName(iDt).joinToString(".")
|
||||
assignExpressionToVariable(expr.left, tmpReg, dt, expr.definingSubroutine)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.A, dt==DataType.BYTE || dt==DataType.WORD)
|
||||
asmgen.out(" and $tmpReg")
|
||||
if(assign.target.datatype in ByteDatatypes)
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
else {
|
||||
asmgen.out(" ldy #0")
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
return true
|
||||
}
|
||||
else throw AssemblyError("weird dt for and, expected byte $expr @${expr.position}")
|
||||
}
|
||||
else if(expr.operator=="or") {
|
||||
val iDt = expr.left.inferType(program)
|
||||
val dt = iDt.getOrElse { throw AssemblyError("weird dt") }
|
||||
if (dt in ByteDatatypes) {
|
||||
val tmpReg = getTempRegisterName(iDt).joinToString(".")
|
||||
assignExpressionToVariable(expr.left, tmpReg, dt, expr.definingSubroutine)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.A, dt==DataType.BYTE || dt==DataType.WORD)
|
||||
asmgen.out(" ora $tmpReg")
|
||||
if(assign.target.datatype in ByteDatatypes)
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
else {
|
||||
asmgen.out(" ldy #0")
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
return true
|
||||
}
|
||||
else throw AssemblyError("weird dt for or, expected byte $expr @${expr.position}")
|
||||
}
|
||||
else if(expr.operator=="xor") {
|
||||
val iDt = expr.left.inferType(program)
|
||||
val dt = iDt.getOrElse { throw AssemblyError("weird dt") }
|
||||
if (dt in ByteDatatypes) {
|
||||
val tmpReg = getTempRegisterName(iDt).joinToString(".")
|
||||
assignExpressionToVariable(expr.left, tmpReg, dt, expr.definingSubroutine)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.A, dt==DataType.BYTE || dt==DataType.WORD)
|
||||
asmgen.out(" eor $tmpReg")
|
||||
if(assign.target.datatype in ByteDatatypes)
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
else {
|
||||
asmgen.out(" ldy #0")
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
return true
|
||||
}
|
||||
else throw AssemblyError("weird dt for xor, expected byte $expr @${expr.position}")
|
||||
}
|
||||
|
||||
if(expr.operator!="+" && expr.operator!="-")
|
||||
return false
|
||||
|
||||
@@ -513,6 +464,77 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
return false
|
||||
}
|
||||
|
||||
private fun attemptAssignToByteCompareZero(expr: BinaryExpression, assign: AsmAssignment): Boolean {
|
||||
when (expr.operator) {
|
||||
"==" -> {
|
||||
when(val dt = expr.left.inferType(program).getOrElse { throw AssemblyError("invalid dt") }) {
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
asmgen.out("""
|
||||
sty P8ZP_SCRATCH_B1
|
||||
ora P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
|
||||
asmgen.out(" jsr floats.SIGN | and #1 | eor #1")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
else->{
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
"!=" -> {
|
||||
when(val dt = expr.left.inferType(program).getOrElse { throw AssemblyError("invalid dt") }) {
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
asmgen.out(" beq + | lda #1")
|
||||
asmgen.out("+")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" beq + | lda #1")
|
||||
asmgen.out("+")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
|
||||
asmgen.out(" jsr floats.SIGN")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
return true
|
||||
}
|
||||
else->{
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun fallbackToStackEval(assign: AsmAssignment) {
|
||||
// TODO DON'T STACK-EVAL... perhaps by using a temp var? so that it becomes augmentable assignment expression?
|
||||
// or don't try to solve it here in this one case and rather rewrite the whole stack based value evaluation.
|
||||
@@ -769,6 +791,20 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
|
||||
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
|
||||
} else {
|
||||
if(!(valueDt isAssignableTo targetDt)) {
|
||||
if(valueDt in WordDatatypes && targetDt in ByteDatatypes) {
|
||||
// word to byte, just take the lsb
|
||||
return assignCastViaLsbFunc(value, target)
|
||||
} else if(valueDt in WordDatatypes && targetDt in WordDatatypes) {
|
||||
// word to word, just assign
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||
} else if(valueDt in ByteDatatypes && targetDt in ByteDatatypes) {
|
||||
// byte to byte, just assign
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||
}
|
||||
else
|
||||
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker")
|
||||
}
|
||||
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
|
||||
}
|
||||
return
|
||||
@@ -2150,10 +2186,7 @@ internal class AssignmentAsmGen(private val program: Program,
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
|
||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.out(
|
||||
" stz cx16.${
|
||||
target.register.toString().lowercase()
|
||||
} | stz cx16.${target.register.toString().lowercase()}+1")
|
||||
asmgen.out(" stz cx16.${target.register.toString().lowercase()} | stz cx16.${target.register.toString().lowercase()}+1")
|
||||
}
|
||||
else -> throw AssemblyError("weird register")
|
||||
}
|
||||
|
||||
+39
-170
@@ -28,7 +28,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"+" -> {}
|
||||
"-" -> inplaceNegate(target, type)
|
||||
"~" -> inplaceInvert(target, type)
|
||||
"not" -> inplaceBooleanNot(target, type)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
}
|
||||
@@ -391,9 +390,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
||||
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||
"&" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
||||
"|" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
||||
"^" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
@@ -428,9 +427,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
"&", "and" -> asmgen.out(" and $otherName")
|
||||
"|", "or" -> asmgen.out(" ora $otherName")
|
||||
"^", "xor" -> asmgen.out(" eor $otherName")
|
||||
"&" -> asmgen.out(" and $otherName")
|
||||
"|" -> asmgen.out(" ora $otherName")
|
||||
"^" -> asmgen.out(" eor $otherName")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
@@ -485,17 +484,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
}
|
||||
"&", "and" -> {
|
||||
"&" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" and #$value")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"|", "or" -> {
|
||||
"|"-> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" ora #$value")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"^", "xor" -> {
|
||||
"^" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" eor #$value")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
@@ -563,15 +562,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
}
|
||||
"&", "and" -> {
|
||||
"&" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" and $name | sta $name")
|
||||
}
|
||||
"|", "or" -> {
|
||||
"|" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" ora $name | sta $name")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
"^" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
@@ -649,9 +648,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
}
|
||||
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name")
|
||||
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name")
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name")
|
||||
"&" -> asmgen.out(" lda $name | and $otherName | sta $name")
|
||||
"|" -> asmgen.out(" lda $name | ora $otherName | sta $name")
|
||||
"^" -> asmgen.out(" lda $name | eor $otherName | sta $name")
|
||||
"==" -> {
|
||||
asmgen.out("""
|
||||
lda $otherName
|
||||
@@ -740,9 +739,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
"&", "and" -> asmgen.out(" lda $name | and #$value | sta $name")
|
||||
"|", "or" -> asmgen.out(" lda $name | ora #$value | sta $name")
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
|
||||
"&" -> asmgen.out(" lda $name | and #$value | sta $name")
|
||||
"|" -> asmgen.out(" lda $name | ora #$value | sta $name")
|
||||
"^" -> asmgen.out(" lda $name | eor #$value | sta $name")
|
||||
"==" -> {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
@@ -785,15 +784,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
sta $name""")
|
||||
}
|
||||
"|", "or" -> {
|
||||
"|" -> {
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" ora $name | sta $name")
|
||||
}
|
||||
"&", "and" -> {
|
||||
"&" -> {
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" and $name | sta $name")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
"^" -> {
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
@@ -828,11 +827,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
dec $name+1
|
||||
+""")
|
||||
}
|
||||
"|", "or" -> {
|
||||
"|" -> {
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" ora $name | sta $name")
|
||||
}
|
||||
"&", "and" -> {
|
||||
"&" -> {
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" and $name | sta $name")
|
||||
if(dt in WordDatatypes) {
|
||||
@@ -842,7 +841,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
}
|
||||
}
|
||||
"^", "xor" -> {
|
||||
"^" -> {
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
@@ -1059,7 +1058,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
"&", "and" -> {
|
||||
"&" -> {
|
||||
when {
|
||||
value == 0 -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
@@ -1096,7 +1095,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else -> asmgen.out(" lda $name | and #<$value | sta $name | lda $name+1 | and #>$value | sta $name+1")
|
||||
}
|
||||
}
|
||||
"|", "or" -> {
|
||||
"|" -> {
|
||||
when {
|
||||
value == 0 -> {}
|
||||
value and 255 == 0 -> asmgen.out(" lda $name+1 | ora #>$value | sta $name+1")
|
||||
@@ -1104,7 +1103,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else -> asmgen.out(" lda $name | ora #<$value | sta $name | lda $name+1 | ora #>$value | sta $name+1")
|
||||
}
|
||||
}
|
||||
"^", "xor" -> {
|
||||
"^" -> {
|
||||
when {
|
||||
value == 0 -> {}
|
||||
value and 255 == 0 -> asmgen.out(" lda $name+1 | eor #>$value | sta $name+1")
|
||||
@@ -1268,7 +1267,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
}
|
||||
"&", "and" -> {
|
||||
"&" -> {
|
||||
asmgen.out(" lda $otherName | and $name | sta $name")
|
||||
if(dt in WordDatatypes) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
@@ -1277,8 +1276,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
}
|
||||
}
|
||||
"|", "or" -> asmgen.out(" lda $otherName | ora $name | sta $name")
|
||||
"^", "xor" -> asmgen.out(" lda $otherName | eor $name | sta $name")
|
||||
"|" -> asmgen.out(" lda $otherName | ora $name | sta $name")
|
||||
"^" -> asmgen.out(" lda $otherName | eor $name | sta $name")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@@ -1349,9 +1348,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
""")
|
||||
}
|
||||
"<<", ">>" -> throw AssemblyError("shift by a word value not supported, max is a byte")
|
||||
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name | lda $name+1 | and $otherName+1 | sta $name+1")
|
||||
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name | lda $name+1 | ora $otherName+1 | sta $name+1")
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name | lda $name+1 | eor $otherName+1 | sta $name+1")
|
||||
"&" -> asmgen.out(" lda $name | and $otherName | sta $name | lda $name+1 | and $otherName+1 | sta $name+1")
|
||||
"|" -> asmgen.out(" lda $name | ora $otherName | sta $name | lda $name+1 | ora $otherName+1 | sta $name+1")
|
||||
"^" -> asmgen.out(" lda $name | eor $otherName | sta $name | lda $name+1 | eor $otherName+1 | sta $name+1")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@@ -1521,7 +1520,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
"&", "and" -> {
|
||||
"&" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" and $name | sta $name")
|
||||
if(dt in WordDatatypes) {
|
||||
@@ -1531,11 +1530,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
}
|
||||
}
|
||||
"|", "or" -> {
|
||||
"|" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" ora $name | sta $name")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
"^" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
@@ -1567,15 +1566,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
remainderVarByWordInAY()
|
||||
}
|
||||
"<<", ">>" -> throw AssemblyError("shift by a word value not supported, max is a byte")
|
||||
"&", "and" -> {
|
||||
"&" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
|
||||
asmgen.out(" and $name | sta $name | tya | and $name+1 | sta $name+1")
|
||||
}
|
||||
"|", "or" -> {
|
||||
"|" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
|
||||
asmgen.out(" ora $name | sta $name | tya | ora $name+1 | sta $name+1")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
"^" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
|
||||
asmgen.out(" eor $name | sta $name | tya | eor $name+1 | sta $name+1")
|
||||
}
|
||||
@@ -1800,136 +1799,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda ${target.asmVarname}
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta ${target.asmVarname}""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val mem = target.memory!!
|
||||
when (mem.addressExpression) {
|
||||
is NumericLiteral -> {
|
||||
val addr = (mem.addressExpression as NumericLiteral).number.toHex()
|
||||
asmgen.out("""
|
||||
lda $addr
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta $addr""")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(mem.addressExpression as IdentifierReference)
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(mem.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out("""
|
||||
cmp #0
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
RegisterOrPair.X -> asmgen.out("""
|
||||
txa
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
tax""")
|
||||
RegisterOrPair.Y -> asmgen.out("""
|
||||
tya
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
tay""")
|
||||
else -> throw AssemblyError("invalid reg dt for byte not")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack not")
|
||||
else -> throw AssemblyError("no asm gen for in-place not of ubyte ${target.kind}")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda ${target.asmVarname}
|
||||
ora ${target.asmVarname}+1
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta ${target.asmVarname}
|
||||
lsr a
|
||||
sta ${target.asmVarname}+1""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.out("""
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ora P8ZP_SCRATCH_REG
|
||||
beq +
|
||||
lda #0
|
||||
tax
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.out("""
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ora P8ZP_SCRATCH_REG
|
||||
beq +
|
||||
lda #0
|
||||
tay
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.out("""
|
||||
stx P8ZP_SCRATCH_REG
|
||||
tya
|
||||
ora P8ZP_SCRATCH_REG
|
||||
beq +
|
||||
ldy #0
|
||||
ldx #0
|
||||
beq ++
|
||||
+ ldx #1
|
||||
+""")
|
||||
}
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
else -> throw AssemblyError("invalid reg dt for word not")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack not")
|
||||
else -> throw AssemblyError("no asm gen for in-place not of uword for ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("boolean-not of invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> {
|
||||
|
||||
@@ -135,6 +135,8 @@ class AstToXmlConverter(internal val program: PtProgram,
|
||||
xml.attr("nosysinit", options.noSysInit.toString())
|
||||
xml.attr("dontreinitglobals", options.dontReinitGlobals.toString())
|
||||
xml.attr("optimize", options.optimize.toString())
|
||||
if(options.evalStackBaseAddress!=null)
|
||||
xml.attr("evalstackbase", options.evalStackBaseAddress!!.toString())
|
||||
if(options.zpReserved.isNotEmpty()) {
|
||||
xml.startChildren()
|
||||
options.zpReserved.forEach {
|
||||
@@ -144,6 +146,15 @@ class AstToXmlConverter(internal val program: PtProgram,
|
||||
xml.endElt()
|
||||
}
|
||||
}
|
||||
if(options.symbolDefs.isNotEmpty()) {
|
||||
xml.startChildren()
|
||||
options.symbolDefs.forEach { name, value ->
|
||||
xml.elt("symboldef")
|
||||
xml.attr("name", name)
|
||||
xml.attr("value", value)
|
||||
xml.endElt()
|
||||
}
|
||||
}
|
||||
xml.endElt()
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ compileTestKotlin {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':virtualmachine')
|
||||
implementation project(':codeAst')
|
||||
implementation project(':codeCore')
|
||||
implementation project(':virtualmachine')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
|
||||
@@ -13,8 +13,7 @@ import kotlin.io.path.bufferedWriter
|
||||
import kotlin.io.path.div
|
||||
|
||||
|
||||
internal class AssemblyProgram(override val name: String,
|
||||
private val allocations: VariableAllocator
|
||||
class AssemblyProgram(override val name: String, private val allocations: VariableAllocator
|
||||
) : IAssemblyProgram {
|
||||
|
||||
private val globalInits = mutableListOf<VmCodeLine>()
|
||||
@@ -68,11 +67,12 @@ internal class AssemblyProgram(override val name: String,
|
||||
|
||||
fun addGlobalInits(chunk: VmCodeChunk) = globalInits.addAll(chunk.lines)
|
||||
fun addBlock(block: VmCodeChunk) = blocks.add(block)
|
||||
fun getBlocks(): List<VmCodeChunk> = blocks
|
||||
}
|
||||
|
||||
internal sealed class VmCodeLine
|
||||
sealed class VmCodeLine
|
||||
|
||||
internal class VmCodeInstruction(
|
||||
class VmCodeInstruction(
|
||||
opcode: Opcode,
|
||||
type: VmDataType?=null,
|
||||
reg1: Int?=null, // 0-$ffff
|
||||
@@ -111,10 +111,10 @@ internal class VmCodeInstruction(
|
||||
}
|
||||
}
|
||||
|
||||
internal class VmCodeLabel(val name: List<String>): VmCodeLine()
|
||||
class VmCodeLabel(val name: List<String>): VmCodeLine()
|
||||
internal class VmCodeComment(val comment: String): VmCodeLine()
|
||||
|
||||
internal class VmCodeChunk(initial: VmCodeLine? = null) {
|
||||
class VmCodeChunk(initial: VmCodeLine? = null) {
|
||||
val lines = mutableListOf<VmCodeLine>()
|
||||
|
||||
init {
|
||||
|
||||
@@ -110,9 +110,6 @@ internal class AssignmentGen(private val codeGen: CodeGen, private val expressio
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
||||
code += VmCodeInstruction(Opcode.XORM, vmDt, reg1=regMask, value = address)
|
||||
}
|
||||
"not" -> {
|
||||
code += VmCodeInstruction(Opcode.NOTM, vmDt, value = address)
|
||||
}
|
||||
else -> throw AssemblyError("weird prefix operator")
|
||||
}
|
||||
return code
|
||||
|
||||
@@ -32,7 +32,6 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
|
||||
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
|
||||
"msb" -> funcMsb(call, resultRegister)
|
||||
"lsb" -> funcLsb(call, resultRegister)
|
||||
"boolean" -> funcBoolean(call, resultRegister)
|
||||
"memory" -> funcMemory(call, resultRegister)
|
||||
"peek" -> funcPeek(call, resultRegister)
|
||||
"peekw" -> funcPeekW(call, resultRegister)
|
||||
@@ -112,21 +111,21 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
|
||||
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
val andReg = codeGen.vmRegisters.nextFree()
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=andReg, value=0x80)
|
||||
code += VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=andReg, reg2=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=andReg, labelSymbol = notNegativeLabel)
|
||||
val compareReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=compareReg, reg2=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=compareReg, value=0x80)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||
code += VmCodeInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
||||
code += VmCodeLabel(notNegativeLabel)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
val andReg = codeGen.vmRegisters.nextFree()
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=andReg, value=0x8000)
|
||||
code += VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=andReg, reg2=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.WORD, reg1=andReg, labelSymbol = notNegativeLabel)
|
||||
val compareReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.WORD, reg1=compareReg, reg2=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=compareReg, value=0x8000)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||
code += VmCodeInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister)
|
||||
code += VmCodeLabel(notNegativeLabel)
|
||||
}
|
||||
@@ -211,7 +210,7 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
|
||||
DataType.ARRAY_UW -> Syscall.SORT_UWORD
|
||||
DataType.ARRAY_W -> Syscall.SORT_WORD
|
||||
DataType.STR -> Syscall.SORT_UBYTE
|
||||
DataType.ARRAY_F -> throw java.lang.IllegalArgumentException("sorting a floating point array is not supported")
|
||||
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
||||
else -> throw IllegalArgumentException("weird type to sort")
|
||||
}
|
||||
val code = VmCodeChunk()
|
||||
@@ -355,30 +354,6 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcBoolean(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val vmDt = codeGen.vmType(call.args[0].type)
|
||||
when (vmDt) {
|
||||
VmDataType.FLOAT -> {
|
||||
val fpValueReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
val fpSignReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += exprGen.translateExpression(call.args.single(), -1, fpValueReg)
|
||||
code += VmCodeInstruction(Opcode.SGN, VmDataType.FLOAT, fpReg1 = fpSignReg, fpReg2 = fpValueReg)
|
||||
code += VmCodeInstruction(Opcode.FTOSB, VmDataType.FLOAT, reg1 = resultRegister, fpReg1 = fpSignReg)
|
||||
}
|
||||
VmDataType.WORD -> {
|
||||
val msbReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = msbReg, reg2=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg)
|
||||
}
|
||||
else -> {
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val vmDt = codeGen.vmType(call.args[0].type)
|
||||
val code = VmCodeChunk()
|
||||
|
||||
@@ -40,7 +40,7 @@ class CodeGen(internal val program: PtProgram,
|
||||
internal val errors: IErrorReporter
|
||||
): IAssemblyGenerator {
|
||||
|
||||
internal val allocations = VariableAllocator(symbolTable, program, errors)
|
||||
internal val allocations = VariableAllocator(symbolTable, program)
|
||||
private val expressionEval = ExpressionGen(this)
|
||||
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
|
||||
private val assignmentGen = AssignmentGen(this, expressionEval)
|
||||
@@ -58,11 +58,21 @@ class CodeGen(internal val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
if(options.symbolDefs.isNotEmpty())
|
||||
throw AssemblyError("virtual target doesn't support symbols defined on the commandline")
|
||||
if(options.evalStackBaseAddress!=null)
|
||||
throw AssemblyError("virtual target doesn't use eval-stack")
|
||||
|
||||
for (block in program.allBlocks()) {
|
||||
vmprog.addBlock(translate(block))
|
||||
}
|
||||
|
||||
println("Vm codegen: amount of vm registers=${vmRegisters.peekNext()}")
|
||||
if(options.optimize) {
|
||||
val optimizer = VmPeepholeOptimizer(vmprog, allocations)
|
||||
optimizer.optimize()
|
||||
}
|
||||
|
||||
println("Vm codegen: virtual registers=${vmRegisters.peekNext()} memory usage=${allocations.freeMem}")
|
||||
|
||||
return vmprog
|
||||
}
|
||||
@@ -339,14 +349,10 @@ class CodeGen(internal val program: PtProgram,
|
||||
code += VmCodeInstruction(Opcode.DEC, dt, reg1=reg)
|
||||
}
|
||||
else -> {
|
||||
val valueReg = vmRegisters.nextFree()
|
||||
if(value>0) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=valueReg, value= value)
|
||||
code += VmCodeInstruction(Opcode.ADD, dt, reg1 = reg, reg2 = valueReg)
|
||||
}
|
||||
else {
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=valueReg, value= -value)
|
||||
code += VmCodeInstruction(Opcode.SUB, dt, reg1 = reg, reg2 = valueReg)
|
||||
code += if(value>0) {
|
||||
VmCodeInstruction(Opcode.ADD, dt, reg1 = reg, value=value)
|
||||
} else {
|
||||
VmCodeInstruction(Opcode.SUB, dt, reg1 = reg, value=-value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,13 +383,13 @@ class CodeGen(internal val program: PtProgram,
|
||||
if(value>0) {
|
||||
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=value)
|
||||
code += VmCodeInstruction(Opcode.ADD, dt, reg1 = valueReg, reg2 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.ADDR, dt, reg1 = valueReg, reg2 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
|
||||
}
|
||||
else {
|
||||
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=-value)
|
||||
code += VmCodeInstruction(Opcode.SUB, dt, reg1 = valueReg, reg2 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.SUBR, dt, reg1 = valueReg, reg2 = operandReg)
|
||||
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
|
||||
}
|
||||
}
|
||||
@@ -395,12 +401,10 @@ class CodeGen(internal val program: PtProgram,
|
||||
val code = VmCodeChunk()
|
||||
if(factor==1f)
|
||||
return code
|
||||
if(factor==0f) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = 0f)
|
||||
code += if(factor==0f) {
|
||||
VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = 0f)
|
||||
} else {
|
||||
val factorReg = vmRegisters.nextFreeFloat()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
||||
code += VmCodeInstruction(Opcode.MUL, VmDataType.FLOAT, fpReg1 = fpReg, fpReg2 = factorReg)
|
||||
VmCodeInstruction(Opcode.MUL, VmDataType.FLOAT, fpReg1 = fpReg, fpValue=factor)
|
||||
}
|
||||
return code
|
||||
}
|
||||
@@ -436,13 +440,10 @@ class CodeGen(internal val program: PtProgram,
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
||||
code += VmCodeInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
|
||||
} else {
|
||||
if (factor == 0) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0)
|
||||
}
|
||||
else {
|
||||
val factorReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
|
||||
code += VmCodeInstruction(Opcode.MUL, dt, reg1=reg, reg2=factorReg)
|
||||
code += if (factor == 0) {
|
||||
VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0)
|
||||
} else {
|
||||
VmCodeInstruction(Opcode.MUL, dt, reg1=reg, value=factor)
|
||||
}
|
||||
}
|
||||
return code
|
||||
@@ -479,12 +480,10 @@ class CodeGen(internal val program: PtProgram,
|
||||
val code = VmCodeChunk()
|
||||
if(factor==1f)
|
||||
return code
|
||||
if(factor==0f) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = Float.MAX_VALUE)
|
||||
code += if(factor==0f) {
|
||||
VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = Float.MAX_VALUE)
|
||||
} else {
|
||||
val factorReg = vmRegisters.nextFreeFloat()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
||||
code += VmCodeInstruction(Opcode.DIVS, VmDataType.FLOAT, fpReg1 = fpReg, fpReg2 = factorReg)
|
||||
VmCodeInstruction(Opcode.DIVS, VmDataType.FLOAT, fpReg1 = fpReg, fpValue=factor)
|
||||
}
|
||||
return code
|
||||
}
|
||||
@@ -526,16 +525,13 @@ class CodeGen(internal val program: PtProgram,
|
||||
else
|
||||
VmCodeInstruction(Opcode.LSRN, dt, reg1=reg, reg2=pow2reg)
|
||||
} else {
|
||||
if (factor == 0) {
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
|
||||
}
|
||||
else {
|
||||
val factorReg = vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.DIVS, dt, reg1=reg, reg2=factorReg)
|
||||
code += if (factor == 0) {
|
||||
VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
|
||||
} else {
|
||||
if(signed)
|
||||
VmCodeInstruction(Opcode.DIVS, dt, reg1=reg, value=factor)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIV, dt, reg1=reg, reg2=factorReg)
|
||||
VmCodeInstruction(Opcode.DIV, dt, reg1=reg, value=factor)
|
||||
}
|
||||
}
|
||||
return code
|
||||
@@ -805,6 +801,7 @@ class CodeGen(internal val program: PtProgram,
|
||||
|
||||
internal fun vmType(type: DataType): VmDataType {
|
||||
return when(type) {
|
||||
DataType.BOOL,
|
||||
DataType.UBYTE,
|
||||
DataType.BYTE -> VmDataType.BYTE
|
||||
DataType.UWORD,
|
||||
|
||||
@@ -149,13 +149,8 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
code += VmCodeInstruction(Opcode.NEG, vmDt, reg1=resultRegister)
|
||||
}
|
||||
"~" -> {
|
||||
val regMask = codeGen.vmRegisters.nextFree()
|
||||
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value=mask)
|
||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=regMask)
|
||||
}
|
||||
"not" -> {
|
||||
code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, value=mask)
|
||||
}
|
||||
else -> throw AssemblyError("weird prefix operator")
|
||||
}
|
||||
@@ -254,9 +249,9 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
"*" -> operatorMultiply(binExpr, vmDt, resultRegister, resultFpRegister)
|
||||
"/" -> operatorDivide(binExpr, vmDt, resultRegister, resultFpRegister, signed)
|
||||
"%" -> operatorModulo(binExpr, vmDt, resultRegister)
|
||||
"|", "or" -> operatorOr(binExpr, vmDt, resultRegister)
|
||||
"&", "and" -> operatorAnd(binExpr, vmDt, resultRegister)
|
||||
"^", "xor" -> operatorXor(binExpr, vmDt, resultRegister)
|
||||
"|" -> operatorOr(binExpr, vmDt, resultRegister)
|
||||
"&" -> operatorAnd(binExpr, vmDt, resultRegister)
|
||||
"^" -> operatorXor(binExpr, vmDt, resultRegister)
|
||||
"<<" -> operatorShiftLeft(binExpr, vmDt, resultRegister)
|
||||
">>" -> operatorShiftRight(binExpr, vmDt, resultRegister, signed)
|
||||
"==" -> operatorEquals(binExpr, vmDt, resultRegister, false)
|
||||
@@ -391,13 +386,9 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
comparisonCall.children.add(binExpr.left)
|
||||
comparisonCall.children.add(binExpr.right)
|
||||
code += translate(comparisonCall, resultRegister, -1)
|
||||
if(notEquals) {
|
||||
val maskReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=maskReg, value=1)
|
||||
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, reg2=maskReg)
|
||||
} else {
|
||||
code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister)
|
||||
}
|
||||
if(!notEquals)
|
||||
code += VmCodeInstruction(Opcode.INV, vmDt, reg1=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, value=1)
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
@@ -467,10 +458,15 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
|
||||
private fun operatorXor(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
if(binExpr.right is PtNumber) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.XORR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
@@ -484,10 +480,15 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
|
||||
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
if(binExpr.right is PtNumber) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.AND, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.ANDR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
@@ -501,10 +502,15 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
|
||||
private fun operatorOr(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.OR, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
if(binExpr.right is PtNumber) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.OR, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.ORR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
@@ -521,9 +527,14 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
throw IllegalArgumentException("floating-point modulo not supported")
|
||||
val code = VmCodeChunk()
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.MOD, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
if(binExpr.right is PtNumber) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.MOD, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
||||
} else {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.MODR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
@@ -544,9 +555,9 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.DIVS, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
|
||||
VmCodeInstruction(Opcode.DIVSR, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIV, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
|
||||
VmCodeInstruction(Opcode.DIVR, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
|
||||
}
|
||||
} else {
|
||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
||||
@@ -555,12 +566,20 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
code += codeGen.divideByConst(vmDt, resultRegister, factor, signed)
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += if(signed)
|
||||
VmCodeInstruction(Opcode.DIVS, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIV, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
if(binExpr.right is PtNumber) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += if (signed)
|
||||
VmCodeInstruction(Opcode.DIVS, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIV, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
||||
} else {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += if (signed)
|
||||
VmCodeInstruction(Opcode.DIVSR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
else
|
||||
VmCodeInstruction(Opcode.DIVR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
@@ -614,7 +633,7 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += VmCodeInstruction(Opcode.MUL, vmDt, fpReg1 = resultFpRegister, fpReg2 = rightResultFpReg)
|
||||
code += VmCodeInstruction(Opcode.MULR, vmDt, fpReg1 = resultFpRegister, fpReg2 = rightResultFpReg)
|
||||
}
|
||||
} else {
|
||||
if(constFactorLeft!=null && constFactorLeft.type!=DataType.FLOAT) {
|
||||
@@ -629,7 +648,7 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.MUL, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
code += VmCodeInstruction(Opcode.MULR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
@@ -668,10 +687,15 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
code += VmCodeInstruction(Opcode.DEC, vmDt, fpReg1 = resultFpRegister)
|
||||
}
|
||||
else {
|
||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += VmCodeInstruction(Opcode.SUB, vmDt, fpReg1=resultFpRegister, fpReg2=rightResultFpReg)
|
||||
if(binExpr.right is PtNumber) {
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += VmCodeInstruction(Opcode.SUB, vmDt, fpReg1 = resultFpRegister, fpValue = (binExpr.right as PtNumber).number.toFloat())
|
||||
} else {
|
||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += VmCodeInstruction(Opcode.SUBR, vmDt, fpReg1 = resultFpRegister, fpReg2 = rightResultFpReg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if((binExpr.right as? PtNumber)?.number==1.0) {
|
||||
@@ -679,10 +703,15 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
code += VmCodeInstruction(Opcode.DEC, vmDt, reg1=resultRegister)
|
||||
}
|
||||
else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.SUB, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
if(binExpr.right is PtNumber) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.SUB, vmDt, reg1 = resultRegister, value = (binExpr.right as PtNumber).number.toInt())
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.SUBR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
@@ -724,10 +753,15 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
code += VmCodeInstruction(Opcode.INC, vmDt, fpReg1=resultFpRegister)
|
||||
}
|
||||
else {
|
||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += VmCodeInstruction(Opcode.ADD, vmDt, fpReg1=resultFpRegister, fpReg2=rightResultFpReg)
|
||||
if(binExpr.right is PtNumber) {
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += VmCodeInstruction(Opcode.ADD, vmDt, fpReg1 = resultFpRegister, fpValue = (binExpr.right as PtNumber).number.toFloat())
|
||||
} else {
|
||||
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += translateExpression(binExpr.left, -1, resultFpRegister)
|
||||
code += translateExpression(binExpr.right, -1, rightResultFpReg)
|
||||
code += VmCodeInstruction(Opcode.ADDR, vmDt, fpReg1 = resultFpRegister, fpReg2 = rightResultFpReg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if((binExpr.left as? PtNumber)?.number==1.0) {
|
||||
@@ -739,10 +773,15 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
code += VmCodeInstruction(Opcode.INC, vmDt, reg1=resultRegister)
|
||||
}
|
||||
else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.ADD, vmDt, reg1=resultRegister, reg2=rightResultReg)
|
||||
if(binExpr.right is PtNumber) {
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.ADD, vmDt, reg1 = resultRegister, value=(binExpr.right as PtNumber).number.toInt())
|
||||
} else {
|
||||
val rightResultReg = codeGen.vmRegisters.nextFree()
|
||||
code += translateExpression(binExpr.left, resultRegister, -1)
|
||||
code += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
code += VmCodeInstruction(Opcode.ADDR, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
|
||||
@@ -4,7 +4,7 @@ import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.*
|
||||
|
||||
class VariableAllocator(private val st: SymbolTable, private val program: PtProgram, errors: IErrorReporter) {
|
||||
class VariableAllocator(private val st: SymbolTable, private val program: PtProgram) {
|
||||
|
||||
private val allocations = mutableMapOf<List<String>, Int>()
|
||||
private var freeMemoryStart: Int
|
||||
@@ -26,6 +26,18 @@ class VariableAllocator(private val st: SymbolTable, private val program: PtProg
|
||||
allocations[variable.scopedName] = nextLocation
|
||||
nextLocation += memsize
|
||||
}
|
||||
for (memvar in st.allMemMappedVariables) {
|
||||
// TODO virtual machine doesn't have memory mapped variables, so treat them as regular allocated variables for now
|
||||
val memsize =
|
||||
when (memvar.dt) {
|
||||
in NumericDatatypes -> program.memsizer.memorySize(memvar.dt)
|
||||
in ArrayDatatypes -> program.memsizer.memorySize(memvar.dt, memvar.length!!)
|
||||
else -> throw InternalCompilerException("weird dt")
|
||||
}
|
||||
|
||||
allocations[memvar.scopedName] = nextLocation
|
||||
nextLocation += memsize
|
||||
}
|
||||
|
||||
freeMemoryStart = nextLocation
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.vm.Instruction
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.VmDataType
|
||||
|
||||
internal class VmOptimizerException(msg: String): Exception(msg)
|
||||
|
||||
|
||||
class VmPeepholeOptimizer(private val vmprog: AssemblyProgram, private val allocations: VariableAllocator) {
|
||||
fun optimize() {
|
||||
vmprog.getBlocks().forEach { block ->
|
||||
do {
|
||||
val indexedInstructions = block.lines.withIndex()
|
||||
.filter { it.value is VmCodeInstruction }
|
||||
.map { IndexedValue(it.index, (it.value as VmCodeInstruction).ins) }
|
||||
val changed = removeNops(block, indexedInstructions)
|
||||
|| removeDoubleLoadsAndStores(block, indexedInstructions) // TODO not yet implemented
|
||||
|| removeUselessArithmetic(block, indexedInstructions)
|
||||
|| removeWeirdBranches(block, indexedInstructions)
|
||||
|| removeDoubleSecClc(block, indexedInstructions)
|
||||
|| cleanupPushPop(block, indexedInstructions)
|
||||
// TODO other optimizations:
|
||||
// more complex optimizations such as unused registers
|
||||
} while(changed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanupPushPop(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
||||
// push followed by pop to same target, or different target->replace with load
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(ins.opcode==Opcode.PUSH) {
|
||||
if(idx < block.lines.size-1) {
|
||||
val insAfter = block.lines[idx+1] as? VmCodeInstruction
|
||||
if(insAfter!=null && insAfter.ins.opcode ==Opcode.POP) {
|
||||
if(ins.reg1==insAfter.ins.reg1) {
|
||||
block.lines.removeAt(idx)
|
||||
block.lines.removeAt(idx)
|
||||
} else {
|
||||
block.lines[idx] = VmCodeInstruction(Opcode.LOADR, ins.type, reg1=insAfter.ins.reg1, reg2=ins.reg1)
|
||||
block.lines.removeAt(idx+1)
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
|
||||
private fun removeDoubleSecClc(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
||||
// double sec, clc
|
||||
// sec+clc or clc+sec
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(ins.opcode==Opcode.SEC || ins.opcode==Opcode.CLC) {
|
||||
if(idx < block.lines.size-1) {
|
||||
val insAfter = block.lines[idx+1] as? VmCodeInstruction
|
||||
if(insAfter?.ins?.opcode == ins.opcode) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
else if(ins.opcode==Opcode.SEC && insAfter?.ins?.opcode==Opcode.CLC) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
else if(ins.opcode==Opcode.CLC && insAfter?.ins?.opcode==Opcode.SEC) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeWeirdBranches(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
||||
// jump/branch to label immediately below
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(ins.opcode==Opcode.JUMP && ins.labelSymbol!=null) {
|
||||
// if jumping to label immediately following this
|
||||
if(idx < block.lines.size-1) {
|
||||
val label = block.lines[idx+1] as? VmCodeLabel
|
||||
if(label?.name == ins.labelSymbol) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeUselessArithmetic(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
||||
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
when (ins.opcode) {
|
||||
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
|
||||
if (ins.value == 1) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.ADD, Opcode.SUB -> {
|
||||
if (ins.value == 1) {
|
||||
block.lines[idx] = VmCodeInstruction(
|
||||
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
|
||||
ins.type,
|
||||
ins.reg1
|
||||
)
|
||||
changed = true
|
||||
} else if (ins.value == 0) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.AND -> {
|
||||
if (ins.value == 0) {
|
||||
block.lines[idx] = VmCodeInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
||||
changed = true
|
||||
} else if (ins.value == 255 && ins.type == VmDataType.BYTE) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
} else if (ins.value == 65535 && ins.type == VmDataType.WORD) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.OR -> {
|
||||
if (ins.value == 0) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
} else if ((ins.value == 255 && ins.type == VmDataType.BYTE) || (ins.value == 65535 && ins.type == VmDataType.WORD)) {
|
||||
block.lines[idx] = VmCodeInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.XOR -> {
|
||||
if (ins.value == 0) {
|
||||
block.lines.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeNops(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if (ins.opcode == Opcode.NOP) {
|
||||
changed = true
|
||||
block.lines.removeAt(idx)
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeDoubleLoadsAndStores(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.forEach { (idx, ins) ->
|
||||
|
||||
// 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 multiple sequential rnd with same reg1, only keep one
|
||||
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
|
||||
// TODO: detect multiple same ands, ors; only keep first
|
||||
// ...
|
||||
}
|
||||
return changed
|
||||
}
|
||||
}
|
||||
|
||||
private interface ICodeChange { // TODO not used? remove?
|
||||
fun perform(block: VmCodeChunk)
|
||||
|
||||
class Remove(val idx: Int): ICodeChange {
|
||||
override fun perform(block: VmCodeChunk) {
|
||||
block.lines.removeAt(idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ class BinExprSplitter(private val program: Program, private val options: Compila
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(options.compTarget.name == VMTarget.NAME)
|
||||
return noModifications // don't split expressions when targeting the vm codegen, it handles nested expressions well
|
||||
|
||||
if(assignment.value.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
|
||||
return noModifications
|
||||
|
||||
|
||||
@@ -22,9 +22,6 @@ class ConstExprEvaluator {
|
||||
"&" -> bitwiseand(left, right)
|
||||
"|" -> bitwiseor(left, right)
|
||||
"^" -> bitwisexor(left, right)
|
||||
"and" -> logicaland(left, right)
|
||||
"or" -> logicalor(left, right)
|
||||
"xor" -> logicalxor(left, right)
|
||||
"<" -> NumericLiteral.fromBoolean(left < right, left.position)
|
||||
">" -> NumericLiteral.fromBoolean(left > right, left.position)
|
||||
"<=" -> NumericLiteral.fromBoolean(left <= right, left.position)
|
||||
@@ -58,57 +55,6 @@ class ConstExprEvaluator {
|
||||
return NumericLiteral(left.type, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun logicalxor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot compute $left locical-bitxor $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logicalor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot compute $left locical-or $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logicaland(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
val error = "cannot compute $left locical-and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteral.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bitwisexor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ExpressionError
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.UndefinedSymbolError
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.maySwapOperandOrder
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.VarDeclType
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.AssociativeOperators
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IntegerDatatypes
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
|
||||
|
||||
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
@@ -36,59 +31,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
// Try to turn a unary prefix expression into a single constant value.
|
||||
// Compile-time constant sub expressions will be evaluated on the spot.
|
||||
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
val subexpr = expr.expression
|
||||
if (subexpr is NumericLiteral) {
|
||||
// accept prefixed literal values (such as -3, not true)
|
||||
return when (expr.operator) {
|
||||
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
|
||||
"-" -> when (subexpr.type) {
|
||||
in IntegerDatatypes -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteral.optimalInteger(-subexpr.number.toInt(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteral(DataType.FLOAT, -subexpr.number, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
}
|
||||
"~" -> when (subexpr.type) {
|
||||
DataType.BYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteral(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteral(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteral(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteral(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
}
|
||||
"not" -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteral.fromBoolean(subexpr.number == 0.0, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
val constValue = expr.constValue(program) ?: return noModifications
|
||||
return listOf(IAstModification.ReplaceNode(expr, constValue, parent))
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -318,7 +262,6 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
// the args of a fuction are constfolded via recursion already.
|
||||
val constvalue = functionCallExpr.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
listOf(IAstModification.ReplaceNode(functionCallExpr, constvalue, parent))
|
||||
@@ -326,6 +269,14 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(bfc: BuiltinFunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = bfc.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
listOf(IAstModification.ReplaceNode(bfc, constvalue, parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteral, targetDt: DataType, rangeTo: NumericLiteral, stepLiteral: NumericLiteral?, range: RangeExpression): RangeExpression? {
|
||||
val fromCast = rangeFrom.cast(targetDt)
|
||||
|
||||
@@ -12,10 +12,7 @@ import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.Jump
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.AssociativeOperators
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IntegerDatatypes
|
||||
import prog8.code.core.NumericDatatypes
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
@@ -196,35 +193,19 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
// boolvar & 1 --> boolvar
|
||||
// boolvar & 2 --> false
|
||||
if(expr.operator=="&" && rightDt in IntegerDatatypes && (leftDt == DataType.BOOL || (expr.left as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true)) {
|
||||
if(rightVal?.number==1.0) {
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
} else if(rightVal?.number!=null && (rightVal.number.toInt() and 1)==0) {
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
}
|
||||
|
||||
// simplify when a term is constant and directly determines the outcome
|
||||
val constTrue = NumericLiteral.fromBoolean(true, expr.position)
|
||||
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
|
||||
val newExpr: Expression? = when (expr.operator) {
|
||||
"or" -> {
|
||||
when {
|
||||
leftVal != null && leftVal.asBooleanValue || rightVal != null && rightVal.asBooleanValue -> constTrue
|
||||
leftVal != null && !leftVal.asBooleanValue -> expr.right
|
||||
rightVal != null && !rightVal.asBooleanValue -> expr.left
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"and" -> {
|
||||
when {
|
||||
leftVal != null && !leftVal.asBooleanValue || rightVal != null && !rightVal.asBooleanValue -> constFalse
|
||||
leftVal != null && leftVal.asBooleanValue -> expr.right
|
||||
rightVal != null && rightVal.asBooleanValue -> expr.left
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"xor" -> {
|
||||
when {
|
||||
leftVal != null && !leftVal.asBooleanValue -> expr.right
|
||||
rightVal != null && !rightVal.asBooleanValue -> expr.left
|
||||
leftVal != null && leftVal.asBooleanValue -> PrefixExpression("not", expr.right, expr.right.position)
|
||||
rightVal != null && rightVal.asBooleanValue -> PrefixExpression("not", expr.left, expr.left.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"|" -> {
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
@@ -268,6 +249,28 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
else -> null
|
||||
}
|
||||
|
||||
if(rightVal!=null && leftDt==DataType.BOOL) {
|
||||
// see if we can replace comparison against true/1 with simpler comparison against zero
|
||||
if (expr.operator == "==") {
|
||||
if (rightVal.number == 1.0) {
|
||||
val zero = NumericLiteral(DataType.UBYTE, 0.0, expr.right.position)
|
||||
return listOf(
|
||||
IAstModification.SetExpression({expr.operator="!="}, expr, parent),
|
||||
IAstModification.ReplaceNode(expr.right, zero, expr)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (expr.operator == "!=") {
|
||||
if (rightVal.number == 1.0) {
|
||||
val zero = NumericLiteral(DataType.UBYTE, 0.0, expr.right.position)
|
||||
return listOf(
|
||||
IAstModification.SetExpression({expr.operator="=="}, expr, parent),
|
||||
IAstModification.ReplaceNode(expr.right, zero, expr)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(newExpr != null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
|
||||
@@ -286,6 +289,12 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg.expression, parent))
|
||||
}
|
||||
} else {
|
||||
if(arg is IdentifierReference && arg.nameInSource.size==2
|
||||
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
|
||||
// lsb(cx16.r0) -> cx16.r0L
|
||||
val highReg = IdentifierReference(listOf("cx16", arg.nameInSource[1]+'L'), arg.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, highReg, parent))
|
||||
}
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
|
||||
// useless lsb() of byte value
|
||||
@@ -307,6 +316,12 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
parent))
|
||||
}
|
||||
} else {
|
||||
if(arg is IdentifierReference && arg.nameInSource.size==2
|
||||
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
|
||||
// msb(cx16.r0) -> cx16.r0H
|
||||
val highReg = IdentifierReference(listOf("cx16", arg.nameInSource[1]+'H'), arg.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, highReg, parent))
|
||||
}
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
|
||||
// useless msb() of byte value, replace with 0
|
||||
@@ -321,25 +336,6 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
val range = containment.iterable as? RangeExpression
|
||||
if(range!=null && range.step.constValue(program)?.number==1.0) {
|
||||
val from = range.from.constValue(program)
|
||||
val to = range.to.constValue(program)
|
||||
val value = containment.element
|
||||
if(from!=null && to!=null && value.isSimple) {
|
||||
if(to.number-from.number>6.0) {
|
||||
// replace containment test with X>=from and X<=to
|
||||
val left = BinaryExpression(value, ">=", from, containment.position)
|
||||
val right = BinaryExpression(value.copy(), "<=", to, containment.position)
|
||||
val comparison = BinaryExpression(left, "and", right, containment.position)
|
||||
return listOf(IAstModification.ReplaceNode(containment, comparison, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||
return when {
|
||||
subBinExpr.left isSameAs x -> subBinExpr.right
|
||||
|
||||
@@ -108,7 +108,7 @@ class StatementOptimizer(private val program: Program,
|
||||
|
||||
// empty true part? switch with the else part
|
||||
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
|
||||
val invertedCondition = PrefixExpression("not", ifElse.condition, ifElse.condition.position)
|
||||
val invertedCondition = BinaryExpression(ifElse.condition, "==", NumericLiteral(DataType.UBYTE, 0.0, ifElse.condition.position), ifElse.condition.position)
|
||||
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
|
||||
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
|
||||
return listOf(
|
||||
|
||||
@@ -34,13 +34,14 @@ dependencies {
|
||||
implementation project(':codeGenCpu6502')
|
||||
implementation project(':codeGenVirtual')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation project(':virtualmachine')
|
||||
implementation 'org.antlr:antlr4-runtime:4.10.1'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.2.3'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
|
||||
@@ -23,5 +23,6 @@
|
||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||
<orderEntry type="module" module-name="codeGenVirtual" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -722,6 +722,7 @@ cx16 {
|
||||
; $1300-$1bff is unused RAM on C128. We'll use $1a00-$1bff as the lo/hi evalstack.
|
||||
; the virtual registers are allocated at the bottom of the eval-stack (should be ample space unless
|
||||
; you're doing insane nesting of expressions...)
|
||||
; NOTE: the memory location of these registers can change based on the "-esa" compiler option
|
||||
&uword r0 = $1b00
|
||||
&uword r1 = $1b02
|
||||
&uword r2 = $1b04
|
||||
|
||||
@@ -685,6 +685,7 @@ cx16 {
|
||||
; (because there's no room for them in the zeropage)
|
||||
; they are allocated at the bottom of the eval-stack (should be ample space unless
|
||||
; you're doing insane nesting of expressions...)
|
||||
; NOTE: the memory location of these registers can change based on the "-esa" compiler option
|
||||
&uword r0 = $cf00
|
||||
&uword r1 = $cf02
|
||||
&uword r2 = $cf04
|
||||
|
||||
@@ -102,6 +102,8 @@ cx16diskio {
|
||||
goto byte_read_loop ; macptr block read not supported, do fallback loop
|
||||
diskio.list_blocks += size
|
||||
bufferpointer += size
|
||||
if msb(bufferpointer) == $c0
|
||||
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
|
||||
num_bytes -= size
|
||||
if c64.READST() & $40 {
|
||||
diskio.f_close() ; end of file, close it
|
||||
|
||||
@@ -28,7 +28,7 @@ gfx2 {
|
||||
uword width = 0
|
||||
uword height = 0
|
||||
ubyte bpp = 0
|
||||
ubyte monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
|
||||
bool monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
|
||||
|
||||
sub screen_mode(ubyte mode) {
|
||||
when mode {
|
||||
@@ -127,7 +127,7 @@ gfx2 {
|
||||
position(0, 0)
|
||||
}
|
||||
|
||||
sub monochrome_stipple(ubyte enable) {
|
||||
sub monochrome_stipple(bool enable) {
|
||||
monochrome_dont_stipple_flag = not enable
|
||||
}
|
||||
|
||||
@@ -683,7 +683,7 @@ _done
|
||||
}
|
||||
}
|
||||
|
||||
sub position2(uword @zp x, uword y, ubyte also_port_1) {
|
||||
sub position2(uword @zp x, uword y, bool also_port_1) {
|
||||
position(x, y)
|
||||
if also_port_1 {
|
||||
when active_mode {
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
%import syslib
|
||||
|
||||
psg {
|
||||
; $1F9C0 - $1F9FF 16 blocks of 4 PSG registers (16 voices)
|
||||
; 00 frequency word LSB
|
||||
; 01 frequency word MSB. freqword = HERZ / 0.3725290298461914
|
||||
; 02 bit 7 =right, bit 6 = left, bits 5-0 = volume 0-63 levels
|
||||
; 03 bit 7,6 = waveform, bits 5-0 = Pulse width 0-63
|
||||
; waveform: 0=pulse, 1=sawtooth, 2=triangle, 3=noise
|
||||
const ubyte PULSE = %00000000
|
||||
const ubyte SAWTOOTH = %01000000
|
||||
const ubyte TRIANGLE = %10000000
|
||||
const ubyte NOISE = %11000000
|
||||
const ubyte LEFT = %01000000
|
||||
const ubyte RIGHT = %10000000
|
||||
|
||||
sub voice(ubyte voice_num, ubyte channel, ubyte volume, ubyte waveform, ubyte pulsewidth) {
|
||||
envelope_states[voice_num] = 255
|
||||
cx16.r0 = $f9c2 + voice_num * 4
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
||||
cx16.VERA_ADDR_M = msb(cx16.r0)
|
||||
cx16.VERA_ADDR_H = 1
|
||||
cx16.VERA_DATA0 = channel | volume
|
||||
cx16.VERA_ADDR_L++
|
||||
cx16.VERA_DATA0 = waveform | pulsewidth
|
||||
envelope_volumes[voice_num] = mkword(volume, 0)
|
||||
envelope_maxvolumes[voice_num] = volume
|
||||
}
|
||||
|
||||
; sub freq_hz(ubyte voice_num, float hertz) {
|
||||
; ; this would rely on floating point math to convert hertz to vera frequency
|
||||
; ; TODO should be replaced by integer math maybe with a lookup table?
|
||||
; uword vera_freq = (hertz / 0.3725290298461914) as uword
|
||||
; freq(voice_num, vera_freq)
|
||||
; }
|
||||
|
||||
sub freq(ubyte voice_num, uword vera_freq) {
|
||||
cx16.r0 = $f9c0 + voice_num * 4
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
||||
cx16.VERA_ADDR_M = msb(cx16.r0)
|
||||
cx16.VERA_ADDR_H = 1
|
||||
cx16.VERA_DATA0 = lsb(vera_freq)
|
||||
cx16.VERA_ADDR_L++
|
||||
cx16.VERA_DATA0 = msb(vera_freq)
|
||||
}
|
||||
|
||||
sub volume(ubyte voice_num, ubyte vol) {
|
||||
cx16.r0 = $f9c2 + voice_num * 4
|
||||
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | vol)
|
||||
envelope_volumes[voice_num] = mkword(vol, 0)
|
||||
envelope_maxvolumes[voice_num] = vol
|
||||
}
|
||||
|
||||
sub pulse_width(ubyte voice_num, ubyte pw) {
|
||||
cx16.r0 = $f9c3 + voice_num * 4
|
||||
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | pw)
|
||||
}
|
||||
|
||||
sub envelope(ubyte voice_num, ubyte maxvolume, ubyte attack, ubyte sustain, ubyte release) {
|
||||
envelope_states[voice_num] = 255
|
||||
envelope_attacks[voice_num] = attack
|
||||
envelope_sustains[voice_num] = sustain
|
||||
envelope_releases[voice_num] = release
|
||||
if attack
|
||||
attack = 0
|
||||
else
|
||||
attack = maxvolume ; max volume when no attack is set
|
||||
envelope_volumes[voice_num] = mkword(attack, 0)
|
||||
envelope_maxvolumes[voice_num] = maxvolume
|
||||
envelope_states[voice_num] = 0
|
||||
}
|
||||
|
||||
sub silent() {
|
||||
for cx16.r1L in 0 to 15 {
|
||||
envelope_states[cx16.r1L] = 255
|
||||
envelope_volumes[cx16.r1L] = 0
|
||||
volume(cx16.r1L, 0)
|
||||
}
|
||||
}
|
||||
|
||||
sub envelopes_irq() {
|
||||
; If you want to use real-time volume envelopes (Attack-Sustain-Release),
|
||||
; you have to call this routine every 1/60th second, for example from your vsync irq handler,
|
||||
; or just install this routine as the only irq handler if you don't have to do other things there.
|
||||
; Example: cx16.set_irq(&psg.envelopes_irq, true)
|
||||
|
||||
; cx16.r0 = the volume word (volume scaled by 256)
|
||||
; cx16.r1L = the voice number
|
||||
; cx16.r2L = attack value
|
||||
|
||||
; calculate new volumes
|
||||
for cx16.r1L in 0 to 15 {
|
||||
when envelope_states[cx16.r1L] {
|
||||
0 -> {
|
||||
; attack
|
||||
cx16.r2L = envelope_maxvolumes[cx16.r1L]
|
||||
cx16.r0 = envelope_volumes[cx16.r1L] + envelope_attacks[cx16.r1L] * $0040
|
||||
if msb(cx16.r0) > cx16.r2L or envelope_attacks[cx16.r1L]==0 {
|
||||
cx16.r0 = mkword(cx16.r2L, 0)
|
||||
envelope_attacks[cx16.r1L] = 0
|
||||
envelope_states[cx16.r1L] = 1 ; start sustain
|
||||
}
|
||||
envelope_volumes[cx16.r1L] = cx16.r0
|
||||
}
|
||||
1 -> {
|
||||
; sustain
|
||||
if envelope_sustains[cx16.r1L] {
|
||||
envelope_sustains[cx16.r1L]--
|
||||
} else {
|
||||
envelope_states[cx16.r1L] = 2 ; start release
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
; release
|
||||
cx16.r0 = envelope_volumes[cx16.r1L] - envelope_releases[cx16.r1L] * $0040
|
||||
if msb(cx16.r0) & %11000000 {
|
||||
cx16.r0 = 0
|
||||
envelope_releases[cx16.r1L] = 0
|
||||
}
|
||||
envelope_volumes[cx16.r1L] = cx16.r0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
; set new volumes of all 16 voices, using vera stride of 4
|
||||
cx16.push_vera_context()
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_L = $c2
|
||||
cx16.VERA_ADDR_M = $f9
|
||||
cx16.VERA_ADDR_H = 1 | %00110000
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_L = $c2
|
||||
cx16.VERA_ADDR_M = $f9
|
||||
cx16.VERA_ADDR_H = 1 | %00110000
|
||||
for cx16.r1L in 0 to 15 {
|
||||
cx16.VERA_DATA0 = cx16.VERA_DATA1 & %11000000 | msb(envelope_volumes[cx16.r1L])
|
||||
}
|
||||
cx16.pop_vera_context()
|
||||
}
|
||||
|
||||
ubyte[16] envelope_states
|
||||
uword[16] envelope_volumes ; scaled by 256
|
||||
ubyte[16] envelope_attacks
|
||||
ubyte[16] envelope_sustains
|
||||
ubyte[16] envelope_releases
|
||||
ubyte[16] envelope_maxvolumes
|
||||
}
|
||||
@@ -639,8 +639,12 @@ asmsub init_system_phase2() {
|
||||
|
||||
asmsub cleanup_at_exit() {
|
||||
; executed when the main subroutine does rts
|
||||
; just an rts here, nothing special to clean up on cx16
|
||||
%asm {{
|
||||
lda #1
|
||||
sta $00 ; ram bank 1
|
||||
lda #4
|
||||
sta $01 ; rom bank 4 (kernal)
|
||||
stz $2d ; hack to reset machine code monitor bank to 0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -731,6 +735,52 @@ IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub push_vera_context() clobbers(A) {
|
||||
; -- use this at the start of your IRQ handler if it uses Vera registers, to save the state
|
||||
%asm {{
|
||||
lda cx16.VERA_ADDR_L
|
||||
pha
|
||||
lda cx16.VERA_ADDR_M
|
||||
pha
|
||||
lda cx16.VERA_ADDR_H
|
||||
pha
|
||||
lda cx16.VERA_CTRL
|
||||
pha
|
||||
eor #1
|
||||
sta cx16.VERA_CTRL
|
||||
lda cx16.VERA_ADDR_L
|
||||
pha
|
||||
lda cx16.VERA_ADDR_M
|
||||
pha
|
||||
lda cx16.VERA_ADDR_H
|
||||
pha
|
||||
lda cx16.VERA_CTRL
|
||||
pha
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pop_vera_context() clobbers(A) {
|
||||
; -- use this at the end of your IRQ handler if it uses Vera registers, to restore the state
|
||||
%asm {{
|
||||
pla
|
||||
sta cx16.VERA_CTRL
|
||||
pla
|
||||
sta cx16.VERA_ADDR_H
|
||||
pla
|
||||
sta cx16.VERA_ADDR_M
|
||||
pla
|
||||
sta cx16.VERA_ADDR_L
|
||||
pla
|
||||
sta cx16.VERA_CTRL
|
||||
pla
|
||||
sta cx16.VERA_ADDR_H
|
||||
pla
|
||||
sta cx16.VERA_ADDR_M
|
||||
pla
|
||||
sta cx16.VERA_ADDR_L
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
diskio {
|
||||
|
||||
sub directory(ubyte drivenumber) -> ubyte {
|
||||
sub directory(ubyte drivenumber) -> bool {
|
||||
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
@@ -61,12 +61,12 @@ io_error:
|
||||
|
||||
|
||||
; internal variables for the iterative file lister / loader
|
||||
ubyte list_skip_disk_name
|
||||
bool list_skip_disk_name
|
||||
uword list_pattern
|
||||
uword list_blocks
|
||||
ubyte iteration_in_progress = false
|
||||
bool iteration_in_progress = false
|
||||
ubyte @zp first_byte
|
||||
ubyte have_first_byte
|
||||
bool have_first_byte
|
||||
str list_filename = "?" * 32
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ io_error:
|
||||
|
||||
; ----- iterative file lister functions (uses io channel 12) -----
|
||||
|
||||
sub lf_start_list(ubyte drivenumber, uword pattern_ptr) -> ubyte {
|
||||
sub lf_start_list(ubyte drivenumber, uword pattern_ptr) -> bool {
|
||||
; -- start an iterative file listing with optional pattern matching.
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
lf_end_list()
|
||||
@@ -127,7 +127,7 @@ io_error:
|
||||
return false
|
||||
}
|
||||
|
||||
sub lf_next_entry() -> ubyte {
|
||||
sub lf_next_entry() -> bool {
|
||||
; -- retrieve the next entry from an iterative file listing session.
|
||||
; results will be found in list_blocks and list_filename.
|
||||
; if it returns false though, there are no more entries (or an error occurred).
|
||||
@@ -199,7 +199,7 @@ close_end:
|
||||
|
||||
; ----- iterative file loader functions (uses io channel 11) -----
|
||||
|
||||
sub f_open(ubyte drivenumber, uword filenameptr) -> ubyte {
|
||||
sub f_open(ubyte drivenumber, uword filenameptr) -> bool {
|
||||
; -- open a file for iterative reading with f_read
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
f_close()
|
||||
@@ -226,6 +226,8 @@ close_end:
|
||||
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
|
||||
; -- read from the currently open file, up to the given number of bytes.
|
||||
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||
; NOTE: on systems with banked ram (such as Commander X16) this routine DOES NOT
|
||||
; automatically load into subsequent banks if it reaches a bank boundary!
|
||||
if not iteration_in_progress or not num_bytes
|
||||
return 0
|
||||
|
||||
@@ -341,7 +343,7 @@ _end rts
|
||||
|
||||
; ----- iterative file saver functions (uses io channel 14) -----
|
||||
|
||||
sub f_open_w(ubyte drivenumber, uword filenameptr) -> ubyte {
|
||||
sub f_open_w(ubyte drivenumber, uword filenameptr) -> bool {
|
||||
; -- open a file for iterative writing with f_write
|
||||
f_close_w()
|
||||
|
||||
@@ -356,7 +358,7 @@ _end rts
|
||||
return false
|
||||
}
|
||||
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> ubyte {
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||
; -- write the given umber of bytes to the currently open file
|
||||
if num_bytes!=0 {
|
||||
void c64.CHKOUT(14) ; use #14 as input channel again
|
||||
@@ -406,7 +408,7 @@ io_error:
|
||||
goto done
|
||||
}
|
||||
|
||||
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
|
||||
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> bool {
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
uword @shared end_address = address + size
|
||||
@@ -476,7 +478,7 @@ io_error:
|
||||
|
||||
; Internal routine, only to be used on Commander X16 platform if headerless=true,
|
||||
; because this routine uses kernal support for that to load headerless files.
|
||||
sub load_headerless_cx16(ubyte drivenumber, uword filenameptr, uword address_override, ubyte headerless) -> uword {
|
||||
sub load_headerless_cx16(ubyte drivenumber, uword filenameptr, uword address_override, bool headerless) -> uword {
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
ubyte secondary = 1
|
||||
cx16.r1 = 0
|
||||
|
||||
@@ -86,9 +86,9 @@ func_all_w_stack .proc
|
||||
|
||||
abs_b_stack .proc
|
||||
; -- push abs(A) on stack (as unsigned word)
|
||||
jsr abs_b_into_A
|
||||
jsr abs_b_into_AY
|
||||
sta P8ESTACK_LO,x
|
||||
stz p8ESTACK_HI,x
|
||||
stz P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
@@ -58,27 +58,6 @@ inv_word .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
not_byte .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
not_word .proc
|
||||
lda P8ESTACK_LO + 1,x
|
||||
ora P8ESTACK_HI + 1,x
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta P8ESTACK_LO + 1,x
|
||||
lsr a
|
||||
sta P8ESTACK_HI + 1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
bitand_b .proc
|
||||
; -- bitwise and (of 2 bytes)
|
||||
lda P8ESTACK_LO+2,x
|
||||
@@ -142,98 +121,6 @@ bitxor_w .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
and_b .proc
|
||||
; -- logical and (of 2 bytes)
|
||||
lda P8ESTACK_LO+2,x
|
||||
beq +
|
||||
lda #1
|
||||
+ sta P8ZP_SCRATCH_B1
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq +
|
||||
lda #1
|
||||
+ and P8ZP_SCRATCH_B1
|
||||
inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
or_b .proc
|
||||
; -- logical or (of 2 bytes)
|
||||
lda P8ESTACK_LO+2,x
|
||||
ora P8ESTACK_LO+1,x
|
||||
beq +
|
||||
lda #1
|
||||
+ inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
xor_b .proc
|
||||
; -- logical xor (of 2 bytes)
|
||||
lda P8ESTACK_LO+2,x
|
||||
beq +
|
||||
lda #1
|
||||
+ sta P8ZP_SCRATCH_B1
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq +
|
||||
lda #1
|
||||
+ eor P8ZP_SCRATCH_B1
|
||||
inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
and_w .proc
|
||||
; -- logical and (word and word -> byte)
|
||||
lda P8ESTACK_LO+2,x
|
||||
ora P8ESTACK_HI+2,x
|
||||
beq +
|
||||
lda #1
|
||||
+ sta P8ZP_SCRATCH_B1
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora P8ESTACK_HI+1,x
|
||||
beq +
|
||||
lda #1
|
||||
+ and P8ZP_SCRATCH_B1
|
||||
inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
sta P8ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
or_w .proc
|
||||
; -- logical or (word or word -> byte)
|
||||
lda P8ESTACK_LO+2,x
|
||||
ora P8ESTACK_LO+1,x
|
||||
ora P8ESTACK_HI+2,x
|
||||
ora P8ESTACK_HI+1,x
|
||||
beq +
|
||||
lda #1
|
||||
+ inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
sta P8ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
xor_w .proc
|
||||
; -- logical xor (word xor word -> byte)
|
||||
lda P8ESTACK_LO+2,x
|
||||
ora P8ESTACK_HI+2,x
|
||||
beq +
|
||||
lda #1
|
||||
+ sta P8ZP_SCRATCH_B1
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora P8ESTACK_HI+1,x
|
||||
beq +
|
||||
lda #1
|
||||
+ eor P8ZP_SCRATCH_B1
|
||||
inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
sta P8ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
add_w .proc
|
||||
; -- push word+word / uword+uword
|
||||
|
||||
@@ -95,13 +95,21 @@ sys {
|
||||
syscall 10
|
||||
}}
|
||||
}
|
||||
|
||||
sub gfx_getpixel(uword xx, uword yy) -> ubyte {
|
||||
%asm {{
|
||||
loadm.w r0, {sys.gfx_getpixel.xx}
|
||||
loadm.w r1, {sys.gfx_getpixel.yy}
|
||||
syscall 30
|
||||
return
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
||||
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
||||
; they are simulated on the VirtualMachine as well but their location in memory is different
|
||||
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
|
||||
&uword r0 = $0002
|
||||
&uword r1 = $0004
|
||||
&uword r2 = $0006
|
||||
|
||||
@@ -1 +1 @@
|
||||
8.2
|
||||
8.3
|
||||
@@ -3,6 +3,7 @@ package prog8
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.code.core.CbmPrgLauncherType
|
||||
import prog8.code.core.toHex
|
||||
import prog8.code.target.*
|
||||
import prog8.code.target.virtual.VirtualMachineDefinition
|
||||
import prog8.compiler.CompilationResult
|
||||
@@ -17,7 +18,6 @@ import java.time.LocalDateTime
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val buildVersion = object {}.javaClass.getResource("/version.txt")?.readText()?.trim()
|
||||
println("\nProg8 compiler v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
|
||||
@@ -46,10 +46,11 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
||||
val asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well")
|
||||
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')")
|
||||
.default(C64Target.NAME)
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')").default(C64Target.NAME)
|
||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a p8-virt listing in the VM instead")
|
||||
val symbolDefs by cli.option(ArgType.String, fullName = "D", description = "define assembly symbol(s) with -D SYMBOL=VALUE").multiple()
|
||||
val evalStackAddrString by cli.option(ArgType.String, fullName = "esa", description = "override the eval-stack base address (must be page aligned)")
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||
|
||||
try {
|
||||
@@ -84,6 +85,27 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
return runVm(moduleFiles.first())
|
||||
}
|
||||
|
||||
var evalStackAddr: UInt? = null
|
||||
if(evalStackAddrString!=null) {
|
||||
try {
|
||||
evalStackAddr = if (evalStackAddrString!!.startsWith("0x"))
|
||||
evalStackAddrString!!.substring(2).toUInt(16)
|
||||
else if (evalStackAddrString!!.startsWith("$"))
|
||||
evalStackAddrString!!.substring(1).toUInt(16)
|
||||
else
|
||||
evalStackAddrString!!.toUInt()
|
||||
} catch(nx: NumberFormatException) {
|
||||
System.err.println("invalid address for evalstack: $evalStackAddrString")
|
||||
return false
|
||||
}
|
||||
if(evalStackAddr !in 256u..65536u-512u || (evalStackAddr and 255u != 0u)) {
|
||||
System.err.println("invalid address for evalstack: ${evalStackAddr.toHex()}")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
val processedSymbols = processSymbolDefs(symbolDefs) ?: return false
|
||||
|
||||
if(watchMode==true) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
val allImportedFiles = mutableSetOf<Path>()
|
||||
@@ -104,6 +126,8 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
asmListfile == true,
|
||||
experimentalCodegen == true,
|
||||
compilationTarget,
|
||||
evalStackAddr,
|
||||
processedSymbols,
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
@@ -156,6 +180,8 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
asmListfile == true,
|
||||
experimentalCodegen == true,
|
||||
compilationTarget,
|
||||
evalStackAddr,
|
||||
processedSymbols,
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
@@ -193,6 +219,21 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
|
||||
val result = mutableMapOf<String, String>()
|
||||
val defPattern = """(.+)\s*=\s*(.+)""".toRegex()
|
||||
for(def in symbolDefs) {
|
||||
val match = defPattern.matchEntire(def.trim())
|
||||
if(match==null) {
|
||||
System.err.println("invalid symbol definition (expected NAME=VALUE): $def")
|
||||
return null
|
||||
}
|
||||
val (_, name, value) = match.groupValues
|
||||
result[name.trim()] = value.trim()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun runVm(listingFilename: String): Boolean {
|
||||
val name =
|
||||
if(listingFilename.endsWith(".p8virt"))
|
||||
|
||||
@@ -38,6 +38,8 @@ class CompilerArguments(val filepath: Path,
|
||||
val asmListfile: Boolean,
|
||||
val experimentalCodegen: Boolean,
|
||||
val compilationTarget: String,
|
||||
val evalStackBaseAddress: UInt?,
|
||||
val symbolDefs: Map<String, String>,
|
||||
val sourceDirs: List<String> = emptyList(),
|
||||
val outputDir: Path = Path(""),
|
||||
val errors: IErrorReporter = ErrorReporter())
|
||||
@@ -77,10 +79,17 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
asmQuiet = args.quietAssembler
|
||||
asmListfile = args.asmListfile
|
||||
experimentalCodegen = args.experimentalCodegen
|
||||
evalStackBaseAddress = args.evalStackBaseAddress
|
||||
outputDir = args.outputDir.normalize()
|
||||
symbolDefs = args.symbolDefs
|
||||
}
|
||||
program = programresult
|
||||
importedFiles = imported
|
||||
|
||||
if(compilationOptions.evalStackBaseAddress!=null) {
|
||||
compTarget.machine.overrideEvalStack(compilationOptions.evalStackBaseAddress!!)
|
||||
}
|
||||
|
||||
processAst(program, args.errors, compilationOptions)
|
||||
if (compilationOptions.optimize) {
|
||||
// println("*********** AST RIGHT BEFORE OPTIMIZING *************")
|
||||
@@ -330,6 +339,8 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
|
||||
errors.report()
|
||||
program.reorderStatements(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.changeNotExpression(errors)
|
||||
errors.report()
|
||||
program.addTypecasts(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.variousCleanups(errors, compilerOptions)
|
||||
|
||||
@@ -10,9 +10,7 @@ import prog8.ast.determineGosubArguments
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.code.core.*
|
||||
import java.io.File
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
@@ -405,7 +403,8 @@ class IntermediateAstMaker(val program: Program) {
|
||||
|
||||
private fun transform(srcExpr: BinaryExpression): PtBinaryExpression {
|
||||
val type = srcExpr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
|
||||
val expr = PtBinaryExpression(srcExpr.operator, type, srcExpr.position)
|
||||
val actualType = if(type==DataType.BOOL) DataType.UBYTE else type
|
||||
val expr = PtBinaryExpression(srcExpr.operator, actualType, srcExpr.position)
|
||||
expr.add(transformExpression(srcExpr.left))
|
||||
expr.add(transformExpression(srcExpr.right))
|
||||
return expr
|
||||
|
||||
@@ -78,16 +78,30 @@ internal class AstChecker(private val program: Program,
|
||||
if(!valueDt.isKnown) {
|
||||
errors.err("return value type mismatch or unknown symbol", returnStmt.value!!.position)
|
||||
} else {
|
||||
if (expectedReturnValues[0] != valueDt.getOr(DataType.UNDEFINED))
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}", returnStmt.value!!.position)
|
||||
if (expectedReturnValues[0] != valueDt.getOr(DataType.UNDEFINED)) {
|
||||
if(valueDt istype DataType.BOOL && expectedReturnValues[0] == DataType.UBYTE) {
|
||||
// if the return value is a bool and the return type is ubyte, allow this. But give a warning.
|
||||
errors.warn("return type of the subroutine should probably be bool instead of ubyte", returnStmt.position)
|
||||
} else if(valueDt istype DataType.UBYTE && expectedReturnValues[0] == DataType.BOOL) {
|
||||
// if the return value is ubyte and the return type is bool, allow this only if value is 0 or 1
|
||||
val returnValue = returnStmt.value as? NumericLiteral
|
||||
if (returnValue == null || returnValue.type != DataType.UBYTE || (returnValue.number!=0.0 && returnValue.number!=1.0)) {
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}",returnStmt.value!!.position)
|
||||
}
|
||||
}
|
||||
else {
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}",returnStmt.value!!.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visit(returnStmt)
|
||||
}
|
||||
|
||||
override fun visit(ifElse: IfElse) {
|
||||
if(!ifElse.condition.inferType(program).isInteger)
|
||||
errors.err("condition value should be an integer type", ifElse.condition.position)
|
||||
val dt = ifElse.condition.inferType(program)
|
||||
if(!dt.isInteger && !dt.istype(DataType.BOOL))
|
||||
errors.err("condition value should be an integer type or bool", ifElse.condition.position)
|
||||
super.visit(ifElse)
|
||||
}
|
||||
|
||||
@@ -119,6 +133,10 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
|
||||
}
|
||||
DataType.BOOL -> {
|
||||
if(iterableDt != DataType.ARRAY_BOOL)
|
||||
errors.err("bool loop variable can only loop over boolean array", forLoop.position)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
||||
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
|
||||
@@ -418,14 +436,16 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
if(!untilLoop.condition.inferType(program).isInteger)
|
||||
errors.err("condition value should be an integer type", untilLoop.condition.position)
|
||||
val dt = untilLoop.condition.inferType(program)
|
||||
if(!dt.isInteger && !dt.istype(DataType.BOOL))
|
||||
errors.err("condition value should be an integer type or bool", untilLoop.condition.position)
|
||||
super.visit(untilLoop)
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop) {
|
||||
if(!whileLoop.condition.inferType(program).isInteger)
|
||||
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
||||
val dt = whileLoop.condition.inferType(program)
|
||||
if(!dt.isInteger && !dt.istype(DataType.BOOL))
|
||||
errors.err("condition value should be an integer type or bool", whileLoop.condition.position)
|
||||
super.visit(whileLoop)
|
||||
}
|
||||
|
||||
@@ -462,6 +482,13 @@ internal class AstChecker(private val program: Program,
|
||||
if(numvalue!=null && targetDt.isKnown)
|
||||
checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue)
|
||||
|
||||
// for now, don't enforce bool type with only logical operators...
|
||||
// if(assignment.isAugmentable && targetDt istype DataType.BOOL) {
|
||||
// val operator = (assignment.value as? BinaryExpression)?.operator
|
||||
// if(operator in InvalidOperatorsForBoolean)
|
||||
// errors.err("can't use boolean operand with this operator $operator", assignment.position)
|
||||
// }
|
||||
|
||||
super.visit(assignment)
|
||||
}
|
||||
|
||||
@@ -823,10 +850,6 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("can only take negative of a signed number type", expr.position)
|
||||
}
|
||||
}
|
||||
else if(expr.operator == "not") {
|
||||
if(dt !in IntegerDatatypes)
|
||||
errors.err("can only use boolean not on integer types", expr.position)
|
||||
}
|
||||
else if(expr.operator == "~") {
|
||||
if(dt !in IntegerDatatypes)
|
||||
errors.err("can only use bitwise invert on integer types", expr.position)
|
||||
@@ -863,11 +886,6 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("remainder can only be used on unsigned integer operands", expr.right.position)
|
||||
}
|
||||
}
|
||||
"and", "or", "xor" -> {
|
||||
// only integer numeric operands accepted
|
||||
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
|
||||
errors.err("logical operator can only be used on boolean operands", expr.right.position)
|
||||
}
|
||||
"&", "|", "^" -> {
|
||||
// only integer numeric operands accepted
|
||||
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
|
||||
@@ -876,15 +894,17 @@ internal class AstChecker(private val program: Program,
|
||||
"in" -> throw FatalAstException("in expression should have been replaced by containmentcheck")
|
||||
}
|
||||
|
||||
if(leftDt !in NumericDatatypes && leftDt != DataType.STR)
|
||||
if(leftDt !in NumericDatatypes && leftDt != DataType.STR && leftDt != DataType.BOOL)
|
||||
errors.err("left operand is not numeric or str", expr.left.position)
|
||||
if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
|
||||
if(rightDt!in NumericDatatypes && rightDt != DataType.STR && rightDt != DataType.BOOL)
|
||||
errors.err("right operand is not numeric or str", expr.right.position)
|
||||
if(leftDt!=rightDt) {
|
||||
if(leftDt==DataType.STR && rightDt in IntegerDatatypes && expr.operator=="*") {
|
||||
// only exception allowed: str * constvalue
|
||||
if(expr.right.constValue(program)==null)
|
||||
errors.err("can only use string repeat with a constant number value", expr.left.position)
|
||||
} else if(leftDt==DataType.BOOL && rightDt in ByteDatatypes || leftDt in ByteDatatypes && rightDt==DataType.BOOL) {
|
||||
// expression with one side BOOL other side (U)BYTE is allowed; bool==byte
|
||||
} else {
|
||||
errors.err("left and right operands aren't the same type", expr.left.position)
|
||||
}
|
||||
@@ -893,10 +913,31 @@ internal class AstChecker(private val program: Program,
|
||||
if(expr.operator !in ComparisonOperators) {
|
||||
if (leftDt == DataType.STR && rightDt == DataType.STR || leftDt in ArrayDatatypes && rightDt in ArrayDatatypes) {
|
||||
// str+str and str*number have already been const evaluated before we get here.
|
||||
errors.err("no computational expressions with strings or arrays are possible", expr.position)
|
||||
errors.err("no computational or logical expressions with strings or arrays are possible", expr.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt==DataType.BOOL || rightDt==DataType.BOOL ||
|
||||
(expr.left as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true ||
|
||||
(expr.right as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true) {
|
||||
if(expr.operator in setOf("<", "<=", ">", ">=")) {
|
||||
errors.err("can't use boolean operand with this comparison operator", expr.position)
|
||||
}
|
||||
// for now, don't enforce bool type with only logical operators...
|
||||
// if(expr.operator in InvalidOperatorsForBoolean && (leftDt==DataType.BOOL || (expr.left as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true)) {
|
||||
// errors.err("can't use boolean operand with this operator ${expr.operator}", expr.left.position)
|
||||
// }
|
||||
if(expr.operator == "==" || expr.operator == "!=") {
|
||||
val leftNum = expr.left.constValue(program)?.number ?: 0.0
|
||||
val rightNum = expr.right.constValue(program)?.number ?: 0.0
|
||||
if(leftNum>1.0 || rightNum>1.0 || leftNum<0.0 || rightNum<0.0) {
|
||||
errors.warn("expression is always false", expr.position)
|
||||
}
|
||||
}
|
||||
if((expr.operator == "/" || expr.operator == "%") && ( rightDt==DataType.BOOL || (expr.right as? TypecastExpression)?.expression?.inferType(program)?.istype(DataType.BOOL)==true)) {
|
||||
errors.err("can't use boolean operand with this operator ${expr.operator}", expr.right.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(typecast: TypecastExpression) {
|
||||
@@ -1081,8 +1122,14 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
|
||||
var regname = ident.nameInSource[1].uppercase()
|
||||
if(regname.endsWith('L') || regname.endsWith('H') || regname.endsWith('s'))
|
||||
regname=regname.substring(0, regname.length-1)
|
||||
val lastLetter = regname.last().lowercaseChar()
|
||||
if(lastLetter in setOf('l', 'h', 's')) {
|
||||
regname = regname.substring(0, regname.length - 1)
|
||||
val lastLetter2 = regname.last().lowercaseChar()
|
||||
if(lastLetter2 in setOf('l', 'h', 's')) {
|
||||
regname = regname.substring(0, regname.length - 1)
|
||||
}
|
||||
}
|
||||
val reg = RegisterOrPair.valueOf(regname)
|
||||
val same = params.filter { it.value.registerOrPair==reg }
|
||||
for(s in same) {
|
||||
@@ -1122,6 +1169,11 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
// else if(postIncrDecr.target.memoryAddress != null) { } // a memory location can always be ++/--
|
||||
|
||||
if(postIncrDecr.target.inferType(program) istype DataType.BOOL) {
|
||||
errors.err("can't use boolean operand with this operator ${postIncrDecr.operator}", postIncrDecr.position)
|
||||
}
|
||||
|
||||
super.visit(postIncrDecr)
|
||||
}
|
||||
|
||||
@@ -1379,6 +1431,9 @@ internal class AstChecker(private val program: Program,
|
||||
if (number < -32768 || number > 32767)
|
||||
return err("value '$number' out of range for word")
|
||||
}
|
||||
DataType.BOOL -> {
|
||||
return true
|
||||
}
|
||||
else -> return err("value of type ${value.type} not compatible with $targetDt")
|
||||
}
|
||||
return true
|
||||
@@ -1438,12 +1493,13 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
val result = when(targetDatatype) {
|
||||
DataType.BYTE -> sourceDatatype== DataType.BYTE
|
||||
DataType.UBYTE -> sourceDatatype== DataType.UBYTE
|
||||
DataType.WORD -> sourceDatatype== DataType.BYTE || sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.WORD
|
||||
DataType.UWORD -> sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.UWORD
|
||||
DataType.BOOL -> sourceDatatype in NumericDatatypes
|
||||
DataType.BYTE -> sourceDatatype == DataType.BYTE || sourceDatatype == DataType.BOOL
|
||||
DataType.UBYTE -> sourceDatatype == DataType.UBYTE || sourceDatatype == DataType.BOOL
|
||||
DataType.WORD -> sourceDatatype in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.BOOL)
|
||||
DataType.UWORD -> sourceDatatype == DataType.UBYTE || sourceDatatype == DataType.UWORD || sourceDatatype == DataType.BOOL
|
||||
DataType.FLOAT -> sourceDatatype in NumericDatatypes
|
||||
DataType.STR -> sourceDatatype== DataType.STR
|
||||
DataType.STR -> sourceDatatype == DataType.STR
|
||||
else -> {
|
||||
errors.err("cannot assign new value to variable of type $targetDatatype", position)
|
||||
false
|
||||
|
||||
@@ -10,6 +10,7 @@ import prog8.ast.statements.VarDeclOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.compiler.printProgram
|
||||
|
||||
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
@@ -21,14 +22,17 @@ internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: Compila
|
||||
}
|
||||
|
||||
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
|
||||
val boolRemover = BoolRemover(this)
|
||||
boolRemover.visit(this)
|
||||
boolRemover.applyModifications()
|
||||
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
|
||||
fixer.visit(this)
|
||||
while(errors.noErrors() && fixer.applyModifications()>0) {
|
||||
while (errors.noErrors() && fixer.applyModifications() > 0) {
|
||||
fixer.visit(this)
|
||||
}
|
||||
val cleaner = BeforeAsmTypecastCleaner(this, errors)
|
||||
cleaner.visit(this)
|
||||
while(errors.noErrors() && cleaner.applyModifications()>0) {
|
||||
while (errors.noErrors() && cleaner.applyModifications() > 0) {
|
||||
cleaner.visit(this)
|
||||
}
|
||||
}
|
||||
@@ -44,6 +48,14 @@ internal fun Program.reorderStatements(errors: IErrorReporter, options: Compilat
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.changeNotExpression(errors: IErrorReporter) {
|
||||
val changer = NotExpressionChanger(this, errors)
|
||||
changer.visit(this)
|
||||
while(errors.noErrors() && changer.applyModifications()>0) {
|
||||
changer.visit(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, errors: IErrorReporter) {
|
||||
val walker = object : AstWalker() {
|
||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
@@ -7,14 +7,45 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.NumericDatatypes
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.Cx16Target
|
||||
|
||||
|
||||
class AstPreprocessor(val program: Program, val errors: IErrorReporter, val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
override fun before(program: Program): Iterable<IAstModification> {
|
||||
if(compTarget.name!=Cx16Target.NAME) {
|
||||
// reset the address of the virtual registers to be inside the evaluation stack.
|
||||
// (we don't do this on CommanderX16 itself as the registers have a fixed location in Zeropage there)
|
||||
val cx16block = program.allBlocks.single { it.name=="cx16" }
|
||||
val memVars = cx16block.statements
|
||||
.filterIsInstance<VarDecl>()
|
||||
.associateBy { it.name }
|
||||
|
||||
val estack = compTarget.machine.ESTACK_HI
|
||||
for(regnum in 0u..15u) {
|
||||
val rX = memVars.getValue("r$regnum")
|
||||
val rXL = memVars.getValue("r${regnum}L")
|
||||
val rXH = memVars.getValue("r${regnum}H")
|
||||
val rXs = memVars.getValue("r${regnum}s")
|
||||
val rXsL = memVars.getValue("r${regnum}sL")
|
||||
val rXsH = memVars.getValue("r${regnum}sH")
|
||||
setAddress(rX, estack + 2u*regnum)
|
||||
setAddress(rXL, estack + 2u*regnum)
|
||||
setAddress(rXH, estack + 2u*regnum +1u)
|
||||
setAddress(rXs, estack + 2u*regnum)
|
||||
setAddress(rXsL, estack + 2u*regnum)
|
||||
setAddress(rXsH, estack + 2u*regnum + 1u)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun setAddress(vardecl: VarDecl, address: UInt) {
|
||||
val oldAddr = vardecl.value as NumericLiteral
|
||||
vardecl.value = NumericLiteral(oldAddr.type, address.toDouble(), oldAddr.position)
|
||||
}
|
||||
|
||||
override fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(char.encoding== Encoding.DEFAULT)
|
||||
char.encoding = compTarget.defaultEncoding
|
||||
@@ -98,6 +129,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
|
||||
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,10 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
// Try to replace A = B <operator> Something by A= B, A = A <operator> Something
|
||||
// this triggers the more efficent augmented assignment code generation more often.
|
||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||
|
||||
if(options.compTarget.name==VMTarget.NAME) // don't apply this optimization for Vm target
|
||||
return noModifications
|
||||
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
|
||||
@@ -120,7 +124,6 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
// Most code generation targets only support subroutine inlining on asmsub subroutines
|
||||
// So we reset the flag here to be sure it doesn't cause problems down the line in the codegen.
|
||||
if(!subroutine.isAsmSubroutine && options.compTarget.name!=VMTarget.NAME)
|
||||
@@ -169,21 +172,36 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
}
|
||||
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
val prefixExpr = ifElse.condition as? PrefixExpression
|
||||
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||
// if not x -> if x==0
|
||||
val binExpr = ifElse.condition as? BinaryExpression
|
||||
if(binExpr==null) {
|
||||
// if x -> if x!=0
|
||||
val booleanExpr = BinaryExpression(
|
||||
prefixExpr.expression,
|
||||
"==",
|
||||
ifElse.condition,
|
||||
"!=",
|
||||
NumericLiteral.optimalInteger(0, ifElse.condition.position),
|
||||
ifElse.condition.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
|
||||
}
|
||||
|
||||
val binExpr = ifElse.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in ComparisonOperators) {
|
||||
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||
if(binExpr.operator !in ComparisonOperators) {
|
||||
val constRight = binExpr.right.constValue(program)
|
||||
if(constRight!=null) {
|
||||
if (binExpr.operator == "+") {
|
||||
// if x+5 -> if x != -5
|
||||
val number = NumericLiteral(constRight.type, -constRight.number, constRight.position)
|
||||
val booleanExpr = BinaryExpression(binExpr.left,"!=", number, ifElse.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
|
||||
}
|
||||
else if (binExpr.operator == "-") {
|
||||
// if x-5 -> if x != 5
|
||||
val number = NumericLiteral(constRight.type, constRight.number, constRight.position)
|
||||
val booleanExpr = BinaryExpression(binExpr.left,"!=", number, ifElse.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
|
||||
}
|
||||
}
|
||||
|
||||
// if x*5 -> if x*5 != 0
|
||||
val booleanExpr = BinaryExpression(
|
||||
ifElse.condition,
|
||||
"!=",
|
||||
@@ -243,8 +261,12 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
var rightAssignment: Assignment? = null
|
||||
var rightOperandReplacement: Expression? = null
|
||||
|
||||
val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall && expr.left !is ContainmentCheck
|
||||
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall && expr.right !is ContainmentCheck
|
||||
val separateLeftExpr = !expr.left.isSimple
|
||||
&& expr.left !is IFunctionCall
|
||||
&& expr.left !is ContainmentCheck
|
||||
val separateRightExpr = !expr.right.isSimple
|
||||
&& expr.right !is IFunctionCall
|
||||
&& expr.right !is ContainmentCheck
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
|
||||
|
||||
@@ -15,6 +15,14 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
private val errors: IErrorReporter
|
||||
) : AstWalker() {
|
||||
|
||||
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.type == DataType.BOOL) {
|
||||
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(DataType.UBYTE, 0.0, typecast.position), typecast.position)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, notZero, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove redundant typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword or even redundant casts (sourcetype = target type).
|
||||
@@ -70,7 +78,6 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
// also convert calls to builtin functions to BuiltinFunctionCall nodes to make things easier for codegen
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
@@ -86,7 +93,6 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallExpr.target.nameInSource.size==1
|
||||
&& functionCallExpr.target.nameInSource[0] in program.builtinFunctions.names) {
|
||||
@@ -123,5 +129,4 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
|
||||
internal class BoolRemover(val program: Program) : AstWalker() {
|
||||
|
||||
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.type == DataType.BOOL) {
|
||||
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(DataType.UBYTE, 0.0, typecast.position), typecast.position)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, notZero, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.datatype==DataType.BOOL) {
|
||||
var newvalue = decl.value
|
||||
if(newvalue is NumericLiteral) {
|
||||
if(newvalue.number!=0.0)
|
||||
newvalue = NumericLiteral(DataType.UBYTE, 1.0, newvalue.position)
|
||||
}
|
||||
val ubyteDecl = VarDecl(decl.type, decl.origin, DataType.UBYTE, decl.zeropage, decl.arraysize, decl.name,
|
||||
newvalue, decl.isArray, decl.sharedWithAsm, decl.subroutineParameter, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl, ubyteDecl, parent))
|
||||
}
|
||||
|
||||
if(decl.datatype==DataType.ARRAY_BOOL) {
|
||||
var newarray = decl.value
|
||||
if(decl.value is ArrayLiteral) {
|
||||
val oldArray = (decl.value as ArrayLiteral).value
|
||||
val convertedArray = oldArray.map {
|
||||
var number: Expression = it
|
||||
if (it is NumericLiteral && it.type == DataType.BOOL)
|
||||
number = NumericLiteral(DataType.UBYTE, if (it.number == 0.0) 0.0 else 1.0, number.position)
|
||||
number
|
||||
}.toTypedArray()
|
||||
newarray = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_UB), convertedArray, decl.position)
|
||||
}
|
||||
val ubyteArrayDecl = VarDecl(decl.type, decl.origin, DataType.ARRAY_UB, decl.zeropage, decl.arraysize, decl.name,
|
||||
newarray, true, decl.sharedWithAsm, decl.subroutineParameter, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// replace BOOL return type and parameters by UBYTE
|
||||
if(subroutine.returntypes.any { it==DataType.BOOL } || subroutine.parameters.any {it.type==DataType.BOOL}) {
|
||||
val newReturnTypes = subroutine.returntypes.map {
|
||||
if(it==DataType.BOOL) DataType.UBYTE else it
|
||||
}
|
||||
val newParams = subroutine.parameters.map {
|
||||
if(it.type==DataType.BOOL) SubroutineParameter(it.name, DataType.UBYTE, it.position) else it
|
||||
}.toMutableList()
|
||||
val newSubroutine = Subroutine(subroutine.name, newParams, newReturnTypes,
|
||||
subroutine.asmParameterRegisters, subroutine.asmReturnvaluesRegisters, subroutine.asmClobbers,
|
||||
subroutine.asmAddress, subroutine.isAsmSubroutine, subroutine.inline, subroutine.statements,
|
||||
subroutine.position)
|
||||
return listOf(IAstModification.ReplaceNode(subroutine, newSubroutine, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
// convert boolean and/or/xor/not operators to bitwise equivalents.
|
||||
// so that codegen only has to work with bitwise boolean operations from now on.
|
||||
// note: this has to be done here and not in BeforeAsmTypecastCleaner! (code size will increase if done there...)
|
||||
if(expr.operator in setOf("and", "or", "xor")) {
|
||||
expr.operator = when(expr.operator) {
|
||||
"and" -> "&"
|
||||
"or" -> "|"
|
||||
"xor" -> "^"
|
||||
else -> "invalid"
|
||||
}
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val newLeft = wrapWithBooleanCastIfNeeded(expr.left, program)
|
||||
val newRight = wrapWithBooleanCastIfNeeded(expr.right, program)
|
||||
if(newLeft!=null)
|
||||
mods += IAstModification.ReplaceNodeSafe(expr.left, newLeft, expr)
|
||||
if(newRight!=null)
|
||||
mods += IAstModification.ReplaceNodeSafe(expr.right, newRight, expr)
|
||||
return mods
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
internal fun wrapWithBooleanCastIfNeeded(expr: Expression, program: Program): Expression? {
|
||||
fun isBoolean(expr: Expression): Boolean {
|
||||
return if(expr.inferType(program) istype DataType.BOOL)
|
||||
true
|
||||
else if(expr is NumericLiteral && expr.type in IntegerDatatypes && (expr.number==0.0 || expr.number==1.0))
|
||||
true
|
||||
else if(expr is BinaryExpression && expr.operator in ComparisonOperators + LogicalOperators)
|
||||
true
|
||||
else if(expr is PrefixExpression && expr.operator == "not")
|
||||
true
|
||||
else if(expr is BinaryExpression && expr.operator in BitwiseOperators) {
|
||||
if(isBoolean(expr.left) && isBoolean(expr.right))
|
||||
true
|
||||
else expr.operator=="&" && expr.right.constValue(program)?.number==1.0 // x & 1 is also a boolean result
|
||||
}
|
||||
else
|
||||
false
|
||||
}
|
||||
|
||||
return if(isBoolean(expr))
|
||||
null
|
||||
else
|
||||
TypecastExpression(expr, DataType.BOOL, true, expr.position)
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.expressions.DirectMemoryRead
|
||||
import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
|
||||
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
internal class CodeDesugarer(val program: Program,
|
||||
private val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
|
||||
// Several changes have already been done by the StatementReorderer !
|
||||
@@ -28,23 +25,11 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
|
||||
// - repeat-forever loops replaced by label+jump.
|
||||
|
||||
|
||||
private val generatedLabelPrefix = "prog8_label_"
|
||||
|
||||
private fun makeLabel(postfix: String, position: Position): Label {
|
||||
generatedLabelSequenceNumber++
|
||||
return Label("${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix", position)
|
||||
}
|
||||
|
||||
private fun jumpLabel(label: Label): Jump {
|
||||
val ident = IdentifierReference(listOf(label.name), label.position)
|
||||
return Jump(null, ident, null, label.position)
|
||||
}
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
|
||||
val label = makeLabel("after", breakStmt.position)
|
||||
val label = program.makeLabel("after", breakStmt.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(breakStmt, jumpLabel(label), parent),
|
||||
IAstModification.ReplaceNode(breakStmt, program.jumpLabel(label), parent),
|
||||
IAstModification.InsertAfter(stmt, label, stmt.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
@@ -71,17 +56,18 @@ do { STUFF } until CONDITION
|
||||
===>
|
||||
_loop:
|
||||
STUFF
|
||||
if not CONDITION
|
||||
if CONDITION==0
|
||||
goto _loop
|
||||
*/
|
||||
val pos = untilLoop.position
|
||||
val loopLabel = makeLabel("untilloop", pos)
|
||||
val notCondition = PrefixExpression("not", untilLoop.condition, pos)
|
||||
val loopLabel = program.makeLabel("untilloop", pos)
|
||||
val ifCondition = invertCondition(untilLoop.condition)
|
||||
?: BinaryExpression(untilLoop.condition, "==", NumericLiteral(DataType.UBYTE, 0.0, pos), pos)
|
||||
val replacement = AnonymousScope(mutableListOf(
|
||||
loopLabel,
|
||||
untilLoop.body,
|
||||
IfElse(notCondition,
|
||||
AnonymousScope(mutableListOf(jumpLabel(loopLabel)), pos),
|
||||
IfElse(ifCondition,
|
||||
AnonymousScope(mutableListOf(program.jumpLabel(loopLabel)), pos),
|
||||
AnonymousScope(mutableListOf(), pos),
|
||||
pos)
|
||||
), pos)
|
||||
@@ -93,23 +79,24 @@ if not CONDITION
|
||||
while CONDITION { STUFF }
|
||||
==>
|
||||
_whileloop:
|
||||
if NOT CONDITION goto _after
|
||||
if CONDITION==0 goto _after
|
||||
STUFF
|
||||
goto _whileloop
|
||||
_after:
|
||||
*/
|
||||
val pos = whileLoop.position
|
||||
val loopLabel = makeLabel("whileloop", pos)
|
||||
val afterLabel = makeLabel("afterwhile", pos)
|
||||
val notCondition = PrefixExpression("not", whileLoop.condition, pos)
|
||||
val loopLabel = program.makeLabel("whileloop", pos)
|
||||
val afterLabel = program.makeLabel("afterwhile", pos)
|
||||
val ifCondition = invertCondition(whileLoop.condition)
|
||||
?: BinaryExpression(whileLoop.condition, "==", NumericLiteral(DataType.UBYTE, 0.0, pos), pos)
|
||||
val replacement = AnonymousScope(mutableListOf(
|
||||
loopLabel,
|
||||
IfElse(notCondition,
|
||||
AnonymousScope(mutableListOf(jumpLabel(afterLabel)), pos),
|
||||
IfElse(ifCondition,
|
||||
AnonymousScope(mutableListOf(program.jumpLabel(afterLabel)), pos),
|
||||
AnonymousScope(mutableListOf(), pos),
|
||||
pos),
|
||||
whileLoop.body,
|
||||
jumpLabel(loopLabel),
|
||||
program.jumpLabel(loopLabel),
|
||||
afterLabel
|
||||
), pos)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop, replacement, parent))
|
||||
@@ -138,8 +125,8 @@ _after:
|
||||
|
||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(repeatLoop.iterations==null) {
|
||||
val label = makeLabel("repeat", repeatLoop.position)
|
||||
val jump = jumpLabel(label)
|
||||
val label = program.makeLabel("repeat", repeatLoop.position)
|
||||
val jump = program.jumpLabel(label)
|
||||
return listOf(
|
||||
IAstModification.InsertFirst(label, repeatLoop.body),
|
||||
IAstModification.InsertLast(jump, repeatLoop.body),
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.IntegerDatatypes
|
||||
|
||||
internal class NotExpressionChanger(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator=="==" || expr.operator=="!=") {
|
||||
val left = expr.left as? BinaryExpression
|
||||
if (left != null) {
|
||||
val rightValue = expr.right.constValue(program)
|
||||
if (rightValue?.number == 0.0 && rightValue.type in IntegerDatatypes) {
|
||||
if (left.operator == "==" && expr.operator == "==") {
|
||||
// (x==something)==0 --> x!=something
|
||||
left.operator = "!="
|
||||
return listOf(IAstModification.ReplaceNode(expr, left, parent))
|
||||
} else if (left.operator == "!=" && expr.operator == "==") {
|
||||
// (x!=something)==0 --> x==something
|
||||
left.operator = "=="
|
||||
return listOf(IAstModification.ReplaceNode(expr, left, parent))
|
||||
} else if (left.operator == "==" && expr.operator == "!=") {
|
||||
// (x==something)!=0 --> x==something
|
||||
left.operator = "=="
|
||||
return listOf(IAstModification.ReplaceNode(expr, left, parent))
|
||||
} else if (left.operator == "!=" && expr.operator == "!=") {
|
||||
// (x!=something)!=0 --> x!=something
|
||||
left.operator = "!="
|
||||
return listOf(IAstModification.ReplaceNode(expr, left, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator == "not") {
|
||||
// not(not(x)) -> x
|
||||
if((expr.expression as? PrefixExpression)?.operator=="not")
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
||||
// not(~x) -> x!=0
|
||||
if((expr.expression as? PrefixExpression)?.operator=="~") {
|
||||
val x = (expr.expression as PrefixExpression).expression
|
||||
val dt = x.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
val notZero = BinaryExpression(x, "!=", NumericLiteral(dt, 0.0, expr.position), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, notZero, parent))
|
||||
}
|
||||
val subBinExpr = expr.expression as? BinaryExpression
|
||||
if(subBinExpr?.operator=="==") {
|
||||
if(subBinExpr.right.constValue(program)?.number==0.0) {
|
||||
// not(x==0) -> x!=0
|
||||
subBinExpr.operator = "!="
|
||||
return listOf(IAstModification.ReplaceNode(expr, subBinExpr, parent))
|
||||
}
|
||||
} else if(subBinExpr?.operator=="!=") {
|
||||
if(subBinExpr.right.constValue(program)?.number==0.0) {
|
||||
// not(x!=0) -> x==0
|
||||
subBinExpr.operator = "=="
|
||||
return listOf(IAstModification.ReplaceNode(expr, subBinExpr, parent))
|
||||
}
|
||||
}
|
||||
|
||||
// all other not(x) --> x==0
|
||||
// this means that "not" will never occur anywhere again in the ast after this
|
||||
val replacement = BinaryExpression(expr.expression, "==", NumericLiteral(DataType.UBYTE,0.0, expr.position), expr.position)
|
||||
return listOf(IAstModification.ReplaceNodeSafe(expr, replacement, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,6 @@ internal class StatementReorderer(val program: Program,
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
|
||||
// but the current assembly code generator for IF statements now also depends on it, so we do it here regardless of optimization.)
|
||||
@@ -247,38 +246,7 @@ internal class StatementReorderer(val program: Program,
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
else if(expr.operator in LogicalOperators) {
|
||||
fun wrapped(expr: Expression): Expression {
|
||||
return if(expr.inferType(program).isBytes)
|
||||
expr
|
||||
else
|
||||
FunctionCallExpression(IdentifierReference(listOf("boolean"), expr.position), mutableListOf(expr), expr.position)
|
||||
}
|
||||
|
||||
fun isLogicalExpr(expr: Expression?): Boolean {
|
||||
if(expr is BinaryExpression && expr.operator in (LogicalOperators + ComparisonOperators))
|
||||
return true
|
||||
if(expr is PrefixExpression && expr.operator in LogicalOperators)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
return if(isLogicalExpr(expr.left)) {
|
||||
if(isLogicalExpr(expr.right))
|
||||
noModifications
|
||||
else
|
||||
listOf(IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr))
|
||||
} else {
|
||||
if(isLogicalExpr(expr.right))
|
||||
listOf(IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr))
|
||||
else {
|
||||
listOf(
|
||||
IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr),
|
||||
IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -338,7 +306,8 @@ internal class StatementReorderer(val program: Program,
|
||||
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
|
||||
|
||||
@@ -72,7 +72,13 @@ internal class SymbolTableMaker: IAstVisitor {
|
||||
StStaticVariable(decl.name, decl.datatype, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
|
||||
}
|
||||
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)
|
||||
VarDeclType.MEMORY -> StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), decl.position)
|
||||
VarDeclType.MEMORY -> {
|
||||
val numElements =
|
||||
if(decl.datatype in ArrayDatatypes)
|
||||
decl.arraysize!!.constIndex()
|
||||
else null
|
||||
StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, decl.position)
|
||||
}
|
||||
}
|
||||
scopestack.peek().add(node)
|
||||
// st.origAstLinks[decl] = node
|
||||
|
||||
@@ -43,62 +43,88 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
||||
val leftCv = expr.left.constValue(program)
|
||||
val rightCv = expr.right.constValue(program)
|
||||
|
||||
// convert a negative operand for bitwise operator to the 2's complement positive number instead
|
||||
if(expr.operator in BitwiseOperators && leftDt.isInteger && rightDt.isInteger) {
|
||||
val leftCv = expr.left.constValue(program)
|
||||
if(leftCv!=null && leftCv.number<0) {
|
||||
val value = if(rightDt.isBytes) 256+leftCv.number else 65536+leftCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.left,
|
||||
NumericLiteral(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position),
|
||||
expr))
|
||||
}
|
||||
val rightCv = expr.right.constValue(program)
|
||||
if(rightCv!=null && rightCv.number<0) {
|
||||
val value = if(leftDt.isBytes) 256+rightCv.number else 65536+rightCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.right,
|
||||
NumericLiteral(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
|
||||
expr))
|
||||
}
|
||||
|
||||
if(leftDt istype DataType.BYTE && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast left to unsigned
|
||||
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
if(leftDt istype DataType.WORD && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast left to unsigned
|
||||
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
if(rightDt istype DataType.BYTE && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast right to unsigned
|
||||
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
|
||||
}
|
||||
if(rightDt istype DataType.WORD && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast right to unsigned
|
||||
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
|
||||
if(toFix!=null) {
|
||||
if(leftDt.isKnown && rightDt.isKnown) {
|
||||
if(expr.operator in LogicalOperators && leftDt.isInteger && rightDt.isInteger) {
|
||||
// see if any of the operands needs conversion to bool
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
when {
|
||||
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr)
|
||||
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr)
|
||||
else -> throw FatalAstException("confused binary expression side")
|
||||
val newLeft = wrapWithBooleanCastIfNeeded(expr.left, program)
|
||||
val newRight = wrapWithBooleanCastIfNeeded(expr.right, program)
|
||||
if(newLeft!=null)
|
||||
modifications += IAstModification.ReplaceNode(expr.left, newLeft, expr)
|
||||
if(newRight!=null)
|
||||
modifications += IAstModification.ReplaceNode(expr.right, newRight, expr)
|
||||
if(modifications.isNotEmpty())
|
||||
return modifications
|
||||
}
|
||||
if(leftDt!=rightDt) {
|
||||
// convert bool type to byte if needed
|
||||
if(leftDt istype DataType.BOOL && rightDt.isBytes && !rightDt.istype(DataType.BOOL)) {
|
||||
if(rightCv==null || (rightCv.number!=1.0 && rightCv.number!=0.0))
|
||||
return listOf(IAstModification.ReplaceNode(expr.left,
|
||||
TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED),true, expr.left.position), expr))
|
||||
} else if(leftDt.isBytes && !leftDt.istype(DataType.BOOL) && rightDt istype DataType.BOOL) {
|
||||
if(leftCv==null || (leftCv.number!=1.0 && leftCv.number!=0.0))
|
||||
return listOf(IAstModification.ReplaceNode(expr.right,
|
||||
TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED),true, expr.right.position), expr))
|
||||
}
|
||||
|
||||
// convert a negative operand for bitwise operator to the 2's complement positive number instead
|
||||
if(expr.operator in BitwiseOperators && leftDt.isInteger && rightDt.isInteger) {
|
||||
if(leftCv!=null && leftCv.number<0) {
|
||||
val value = if(rightDt.isBytes) 256+leftCv.number else 65536+leftCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.left,
|
||||
NumericLiteral(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position),
|
||||
expr))
|
||||
}
|
||||
if(rightCv!=null && rightCv.number<0) {
|
||||
val value = if(leftDt.isBytes) 256+rightCv.number else 65536+rightCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.right,
|
||||
NumericLiteral(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
|
||||
expr))
|
||||
}
|
||||
|
||||
if(leftDt istype DataType.BYTE && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast left to unsigned
|
||||
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
if(leftDt istype DataType.WORD && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast left to unsigned
|
||||
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
if(rightDt istype DataType.BYTE && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast right to unsigned
|
||||
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
|
||||
}
|
||||
if(rightDt istype DataType.WORD && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast right to unsigned
|
||||
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
|
||||
if(toFix!=null) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
when {
|
||||
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr)
|
||||
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr)
|
||||
else -> throw FatalAstException("confused binary expression side")
|
||||
}
|
||||
return modifications
|
||||
}
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -194,9 +220,22 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
arg,
|
||||
cast.valueOrZero(),
|
||||
call as Node)
|
||||
} else if(requiredType==DataType.BOOL && argtype!=DataType.BOOL) {
|
||||
// cast to bool
|
||||
addTypecastOrCastedValueModification(modifications, arg, requiredType, call as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the argument is an identifier reference and the param is UWORD, add the missing &.
|
||||
if(arg is IdentifierReference && DataType.UWORD == param.type) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
arg,
|
||||
AddressOf(arg, arg.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
@@ -226,6 +265,16 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the argument is an identifier reference and the param is UWORD, add the missing &.
|
||||
if(pair.second is IdentifierReference && DataType.UWORD in pair.first.possibleDatatypes) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
AddressOf(pair.second as IdentifierReference, pair.second.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> { }
|
||||
|
||||
@@ -6,9 +6,7 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
@@ -48,11 +46,18 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
if(parent is Assignment) {
|
||||
val targetDt = (parent).target.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(sourceDt istype targetDt) {
|
||||
// we can get rid of this typecast because the type is already
|
||||
// we can get rid of this typecast because the type is already the target type
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
|
||||
// if the expression is a comparison expression, or a logical expression, it produces the
|
||||
// correct 'boolean' byte result so the cast can be removed.
|
||||
val binExpr = typecast.expression as? BinaryExpression
|
||||
if(binExpr!=null && binExpr.operator in ComparisonOperators + LogicalOperators) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, binExpr, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -71,31 +76,15 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
// +X --> X
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
||||
}
|
||||
if(expr.operator=="not") {
|
||||
val nestedPrefix = expr.expression as? PrefixExpression
|
||||
if(nestedPrefix!=null && nestedPrefix.operator=="not") {
|
||||
// NOT NOT X --> X
|
||||
return listOf(IAstModification.ReplaceNode(expr, nestedPrefix.expression, parent))
|
||||
}
|
||||
val comparison = expr.expression as? BinaryExpression
|
||||
if (comparison != null) {
|
||||
// NOT COMPARISON ==> inverted COMPARISON
|
||||
val invertedOperator = invertedComparisonOperator(comparison.operator)
|
||||
if (invertedOperator != null) {
|
||||
comparison.operator = invertedOperator
|
||||
return listOf(IAstModification.ReplaceNode(expr, comparison, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
// try to replace a multi-comparison expression (if x==1 or x==2 or x==3 ... ) by a simple containment check.
|
||||
// try to replace a multi-comparison expression (if x==1 | x==2 | x==3 ... ) by a simple containment check.
|
||||
// but only if the containment check is the top-level expression.
|
||||
if(parent is BinaryExpression)
|
||||
return noModifications
|
||||
if(expr.operator == "or") {
|
||||
if(expr.operator == "|" || expr.operator=="or") {
|
||||
val leftBinExpr1 = expr.left as? BinaryExpression
|
||||
val rightBinExpr1 = expr.right as? BinaryExpression
|
||||
|
||||
@@ -111,7 +100,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
return false
|
||||
}
|
||||
if(expr.operator!="or")
|
||||
if(expr.operator!="|" && expr.operator!="or")
|
||||
return false
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
@@ -169,7 +158,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
|
||||
fun replaceWithFalse(): Iterable<IAstModification> {
|
||||
errors.warn("condition is always false", containment.position)
|
||||
return listOf(IAstModification.ReplaceNode(containment, NumericLiteral.fromBoolean(false, containment.position), parent))
|
||||
return listOf(IAstModification.ReplaceNode(containment, NumericLiteral(DataType.UBYTE, 0.0, containment.position), parent))
|
||||
}
|
||||
|
||||
fun checkArray(array: Array<Expression>): Iterable<IAstModification> {
|
||||
@@ -233,14 +222,5 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallExpr.target.nameInSource==listOf("boolean")) {
|
||||
if(functionCallExpr.args[0].inferType(program).isBytes) {
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +55,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
return Pair("invalid number of arguments", call.position)
|
||||
val mismatch = argtypes.zip(consideredParamTypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
|
||||
if(mismatch>=0) {
|
||||
val actual = argtypes[mismatch].toString()
|
||||
val expected = consideredParamTypes[mismatch].toString()
|
||||
return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
|
||||
val actual = argtypes[mismatch]
|
||||
val expected = consideredParamTypes[mismatch]
|
||||
if(expected==DataType.BOOL && actual==DataType.UBYTE && call.args[mismatch].constValue(program)?.number !in setOf(0.0, 1.0))
|
||||
return Pair("argument ${mismatch + 1} type mismatch, was: $actual expected: $expected", call.args[mismatch].position)
|
||||
}
|
||||
if(target.isAsmSubroutine) {
|
||||
if(target.asmReturnvaluesRegisters.size>1) {
|
||||
@@ -85,9 +86,9 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
argtypes.zip(consideredParamTypes).forEachIndexed { index, pair ->
|
||||
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
|
||||
if (!anyCompatible) {
|
||||
val actual = pair.first.toString()
|
||||
val actual = pair.first
|
||||
return if(pair.second.size==1) {
|
||||
val expected = pair.second[0].toString()
|
||||
val expected = pair.second[0]
|
||||
Pair("argument ${index + 1} type mismatch, was: $actual expected: $expected", call.args[index].position)
|
||||
} else {
|
||||
val expected = pair.second.toList().toString()
|
||||
|
||||
@@ -39,9 +39,9 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
context("WithInvalidPath") {
|
||||
test("testNonexisting") {
|
||||
val dirRel = Helpers.assumeDirectory(".", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
|
||||
val srcPathRel = Helpers.assumeNotExists(dirRel, "i_do_not_exist")
|
||||
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
|
||||
withClue(".file should be normalized") {
|
||||
@@ -62,7 +62,7 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
|
||||
test("testDirectory") {
|
||||
val srcPathRel = Helpers.assumeDirectory(Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
@@ -95,11 +95,11 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
test("testAbsolute") {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(Helpers.workingDir.relativize(Helpers.fixturesDir)), // we do want a dot "." in front
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = Helpers.assumeReadableFile(searchIn[0], fileName)
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
|
||||
val module = importer.importModule(path.absolute()).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
@@ -109,11 +109,11 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
test("testRelativeToWorkingDir") {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(Helpers.workingDir.relativize(Helpers.fixturesDir)), // we do want a dot "." in front
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = Helpers.assumeReadableFile(searchIn[0], fileName)
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
withClue("sanity check: path should NOT be absolute") {
|
||||
path.isAbsolute shouldBe false
|
||||
}
|
||||
@@ -126,12 +126,12 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
test("testRelativeTo1stDirInSearchList") {
|
||||
val searchIn = Path(".")
|
||||
.div(Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
.div(workingDir.relativize(fixturesDir))
|
||||
.invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = Path(".", fileName)
|
||||
Helpers.assumeReadableFile(searchIn, path)
|
||||
assumeReadableFile(searchIn, path)
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
@@ -141,9 +141,9 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
context("WithBadFile") {
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(srcPath) }
|
||||
|
||||
@@ -160,10 +160,10 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
|
||||
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = Helpers.assumeReadableFile(Helpers.fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = Helpers.assumeReadableFile(Helpers.fixturesDir, "file_with_syntax_error.p8")
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(importing) }
|
||||
|
||||
@@ -194,11 +194,11 @@ class TestModuleImporter: FunSpec({
|
||||
context("ImportLibraryModule") {
|
||||
context("WithInvalidName") {
|
||||
test("testWithNonExistingName") {
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val errors = ErrorReporterForTests(false)
|
||||
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
|
||||
val filenameNoExt = Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist").name
|
||||
val filenameWithExt = Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist.p8").name
|
||||
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
|
||||
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
|
||||
|
||||
repeat(2) { n ->
|
||||
val result = importer.importLibraryModule(filenameNoExt)
|
||||
@@ -221,9 +221,9 @@ class TestModuleImporter: FunSpec({
|
||||
context("WithValidName") {
|
||||
context("WithBadFile") {
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>()
|
||||
@@ -241,10 +241,10 @@ class TestModuleImporter: FunSpec({
|
||||
|
||||
|
||||
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = Helpers.assumeDirectory("./", Helpers.workingDir.relativize(Helpers.fixturesDir))
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = Helpers.assumeReadableFile(Helpers.fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = Helpers.assumeReadableFile(Helpers.fixturesDir, "file_with_syntax_error.p8")
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
|
||||
|
||||
|
||||
@@ -2,19 +2,35 @@ package prog8tests
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.NumericDatatypes
|
||||
import prog8.code.core.NumericDatatypesNoBool
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestBuiltinFunctions: FunSpec({
|
||||
|
||||
test("push pop") {
|
||||
val src="""
|
||||
main {
|
||||
sub start () {
|
||||
pushw(cx16.r0)
|
||||
push(cx16.r1L)
|
||||
pop(cx16.r1L)
|
||||
popw(cx16.r0)
|
||||
}
|
||||
}"""
|
||||
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("pure func with fixed type") {
|
||||
val func = BuiltinFunctions.getValue("sgn")
|
||||
func.name shouldBe "sgn"
|
||||
func.parameters.size shouldBe 1
|
||||
func.parameters[0].name shouldBe "value"
|
||||
func.parameters[0].possibleDatatypes shouldBe NumericDatatypes
|
||||
func.parameters[0].possibleDatatypes shouldBe NumericDatatypesNoBool
|
||||
func.pure shouldBe true
|
||||
func.returnType shouldBe DataType.BYTE
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ import prog8.code.target.Cx16Target
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8tests.helpers.Combinations
|
||||
import prog8tests.helpers.Helpers
|
||||
import prog8tests.helpers.assumeDirectory
|
||||
import prog8tests.helpers.cartesianProduct
|
||||
import prog8tests.helpers.outputDir
|
||||
import prog8tests.helpers.workingDir
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.exists
|
||||
@@ -21,7 +23,7 @@ import kotlin.io.path.exists
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
|
||||
private val examplesDir = Helpers.assumeDirectory(Helpers.workingDir, "../examples")
|
||||
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||
|
||||
private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilationTarget): CompilationResult? {
|
||||
val args = CompilerArguments(
|
||||
@@ -35,7 +37,9 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
compilationTarget = target.name,
|
||||
outputDir = Helpers.outputDir
|
||||
evalStackBaseAddress = null,
|
||||
symbolDefs = emptyMap(),
|
||||
outputDir = outputDir
|
||||
)
|
||||
return compileProgram(args)
|
||||
}
|
||||
@@ -43,12 +47,12 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
|
||||
val searchIn = mutableListOf(examplesDir)
|
||||
if (target is Cx16Target) {
|
||||
searchIn.add(0, Helpers.assumeDirectory(examplesDir, "cx16"))
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn.asSequence()
|
||||
.map { it.resolve("$source.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { Helpers.workingDir.relativize(it) }
|
||||
.map { workingDir.relativize(it) }
|
||||
.first { it.exists() }
|
||||
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${target.name}, optimize=$optimize"
|
||||
return Pair(displayName, filepath)
|
||||
@@ -57,7 +61,7 @@ private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompila
|
||||
|
||||
class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
val onlyC64 = Combinations.cartesianProduct(
|
||||
val onlyC64 = cartesianProduct(
|
||||
listOf(
|
||||
"balloonflight",
|
||||
"bdmusic",
|
||||
@@ -84,16 +88,18 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
val onlyCx16 = Combinations.cartesianProduct(
|
||||
val onlyCx16 = cartesianProduct(
|
||||
listOf(
|
||||
"vtui/testvtui",
|
||||
"amiga",
|
||||
"bdmusic",
|
||||
"bobs",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
"datetime",
|
||||
"highresbitmap",
|
||||
"kefrenbars",
|
||||
"mandelbrot",
|
||||
"mandelbrot-gfx-colors",
|
||||
"multipalette",
|
||||
"rasterbars",
|
||||
@@ -116,7 +122,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
|
||||
val bothCx16AndC64 = Combinations.cartesianProduct(
|
||||
val bothCx16AndC64 = cartesianProduct(
|
||||
listOf(
|
||||
"animals",
|
||||
"balls",
|
||||
@@ -128,6 +134,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
"fibonacci",
|
||||
"line-circle-gfx",
|
||||
"line-circle-txt",
|
||||
"maze",
|
||||
"mandelbrot",
|
||||
"mandelbrot-gfx",
|
||||
"numbergame",
|
||||
|
||||
@@ -10,8 +10,7 @@ import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Label
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.Helpers
|
||||
import prog8tests.helpers.compileFile
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.name
|
||||
|
||||
|
||||
@@ -25,11 +24,11 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
context("Import") {
|
||||
|
||||
test("testImportFromSameFolder") {
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.fixturesDir, "importFromSameFolder.p8")
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, "foo_bar.p8")
|
||||
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.p8")
|
||||
|
||||
val platform = Cx16Target()
|
||||
val result = compileFile(platform, optimize = false, Helpers.fixturesDir, filepath.name)!!
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
|
||||
|
||||
val program = result.program
|
||||
val startSub = program.entrypoint
|
||||
@@ -47,11 +46,11 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
|
||||
context("AsmInclude") {
|
||||
test("testAsmIncludeFromSameFolder") {
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, "foo_bar.asm")
|
||||
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.asm")
|
||||
|
||||
val platform = Cx16Target()
|
||||
val result = compileFile(platform, optimize = false, Helpers.fixturesDir, filepath.name)!!
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
|
||||
|
||||
val program = result.program
|
||||
val startSub = program.entrypoint
|
||||
@@ -72,17 +71,17 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
|
||||
context("Asmbinary") {
|
||||
test("testAsmbinaryDirectiveWithNonExistingFile") {
|
||||
val p8Path = Helpers.assumeReadableFile(Helpers.fixturesDir, "asmBinaryNonExisting.p8")
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist.bin")
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
|
||||
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, Helpers.outputDir) shouldBe null
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir) shouldBe null
|
||||
}
|
||||
|
||||
test("testAsmbinaryDirectiveWithNonReadableFile") {
|
||||
val p8Path = Helpers.assumeReadableFile(Helpers.fixturesDir, "asmBinaryNonReadable.p8")
|
||||
Helpers.assumeDirectory(Helpers.fixturesDir, "subFolder")
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
|
||||
assumeDirectory(fixturesDir, "subFolder")
|
||||
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, Helpers.outputDir) shouldBe null
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir) shouldBe null
|
||||
}
|
||||
|
||||
val tests = listOf(
|
||||
@@ -93,18 +92,18 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
tests.forEach {
|
||||
val (where, p8Str, _) = it
|
||||
test("%asmbinary from ${where}folder") {
|
||||
val p8Path = Helpers.assumeReadableFile(Helpers.fixturesDir, p8Str)
|
||||
// val binPath = Helpers.assumeReadableFile(Helpers.fixturesDir, binStr)
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
// val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
|
||||
// the bug we're testing for (#54) was hidden if outputDir == workingDir
|
||||
withClue("sanity check: workingDir and outputDir should not be the same folder") {
|
||||
Helpers.outputDir.normalize().toAbsolutePath() shouldNotBe Helpers.workingDir.normalize().toAbsolutePath()
|
||||
outputDir.normalize().toAbsolutePath() shouldNotBe workingDir.normalize().toAbsolutePath()
|
||||
}
|
||||
|
||||
withClue("argument to assembler directive .binary " +
|
||||
"should be relative to the generated .asm file (in output dir), " +
|
||||
"NOT relative to .p8 neither current working dir") {
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, Helpers.outputDir) shouldNotBe null
|
||||
compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir) shouldNotBe null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import prog8.code.core.Encoding
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.Combinations
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.cartesianProduct
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
context("floatArrayInitializerWithRange") {
|
||||
val combos = Combinations.cartesianProduct(
|
||||
val combos = cartesianProduct(
|
||||
listOf("", "42", "41"), // sizeInDecl
|
||||
listOf("%import floats", ""), // optEnableFloats
|
||||
listOf(Cx16Target(), C64Target()), // platform
|
||||
|
||||
@@ -6,7 +6,10 @@ import prog8.code.target.Cx16Target
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8tests.helpers.Helpers
|
||||
import prog8tests.helpers.assumeReadableFile
|
||||
import prog8tests.helpers.fixturesDir
|
||||
import prog8tests.helpers.outputDir
|
||||
import prog8tests.helpers.workingDir
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.createTempFile
|
||||
@@ -23,7 +26,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
lateinit var tempFileInWorkingDir: Path
|
||||
|
||||
beforeSpec {
|
||||
tempFileInWorkingDir = createTempFile(directory = Helpers.workingDir, prefix = "tmp_", suffix = ".p8")
|
||||
tempFileInWorkingDir = createTempFile(directory = workingDir, prefix = "tmp_", suffix = ".p8")
|
||||
.also { it.writeText("""
|
||||
main {
|
||||
sub start() {
|
||||
@@ -48,40 +51,42 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
compilationTarget = Cx16Target.NAME,
|
||||
evalStackBaseAddress = null,
|
||||
symbolDefs = emptyMap(),
|
||||
sourceDirs,
|
||||
Helpers.outputDir
|
||||
outputDir
|
||||
)
|
||||
return compileProgram(args)
|
||||
}
|
||||
|
||||
test("testAbsoluteFilePathInWorkingDir") {
|
||||
val filepath = Helpers.assumeReadableFile(tempFileInWorkingDir.absolute())
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
|
||||
compileFile(filepath, listOf()) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testFilePathInWorkingDirRelativeToWorkingDir") {
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.workingDir.relativize(tempFileInWorkingDir.absolute()))
|
||||
val filepath = assumeReadableFile(workingDir.relativize(tempFileInWorkingDir.absolute()))
|
||||
compileFile(filepath, listOf()) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testFilePathInWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = Helpers.assumeReadableFile(tempFileInWorkingDir)
|
||||
compileFile(filepath.fileName, listOf(Helpers.workingDir.toString())) shouldNotBe null
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir)
|
||||
compileFile(filepath.fileName, listOf(workingDir.toString())) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testAbsoluteFilePathOutsideWorkingDir") {
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
compileFile(filepath.absolute(), listOf()) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testFilePathOutsideWorkingDirRelativeToWorkingDir") {
|
||||
val filepath = Helpers.workingDir.relativize(Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8").absolute())
|
||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "ast_simple_main.p8").absolute())
|
||||
compileFile(filepath, listOf()) shouldNotBe null
|
||||
}
|
||||
|
||||
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val sourcedirs = listOf("${Helpers.fixturesDir}")
|
||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val sourcedirs = listOf("${fixturesDir}")
|
||||
compileFile(filepath.fileName, sourcedirs) shouldNotBe null
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import prog8.code.target.C64Target
|
||||
import prog8.compiler.determineCompilationOptions
|
||||
import prog8.compiler.parseImports
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.Helpers
|
||||
import prog8tests.helpers.compileText
|
||||
import prog8tests.helpers.outputDir
|
||||
|
||||
|
||||
class TestImportedModulesOrderAndOptions: FunSpec({
|
||||
@@ -85,7 +85,7 @@ main {
|
||||
}
|
||||
"""
|
||||
val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16)
|
||||
val filepath = Helpers.outputDir.resolve("$filenameBase.p8")
|
||||
val filepath = outputDir.resolve("$filenameBase.p8")
|
||||
filepath.toFile().writeText(sourceText)
|
||||
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target(), emptyList())
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.string.shouldNotBeBlank
|
||||
import io.kotest.matchers.string.shouldStartWith
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
||||
@@ -14,9 +12,9 @@ import prog8.ast.ParentSentinel
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.astprocessing.processAstBeforeAsmGeneration
|
||||
import prog8.compiler.printProgram
|
||||
import prog8tests.helpers.*
|
||||
|
||||
@@ -253,81 +251,30 @@ class TestOptimization: FunSpec({
|
||||
(initY2.value as NumericLiteral).number shouldBe 11.0
|
||||
}
|
||||
|
||||
test("not-typecasted assignment from ubyte logical expression to uword var should be auto upcasted") {
|
||||
test("various 'not' operator rewrites even without optimizations on") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte bb
|
||||
uword ww
|
||||
ww = not bb or not ww ; expression combining ubyte and uword
|
||||
ubyte a1
|
||||
ubyte a2
|
||||
a1 = not not a1 ; a1 = a1==0
|
||||
a1 = not a1 or not a2 ; a1 = a1==0 | a2==0
|
||||
a1 = not a1 and not a2 ; a1 = a1==0 & a2==0
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
|
||||
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
|
||||
printProgram(result.program)
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 8
|
||||
|
||||
val wwAssign = result.program.entrypoint.statements.last() as Assignment
|
||||
val expr = wwAssign.value as TypecastExpression
|
||||
expr.type shouldBe DataType.UWORD
|
||||
|
||||
wwAssign.target.identifier?.nameInSource shouldBe listOf("ww")
|
||||
expr.expression.inferType(result.program) istype DataType.UBYTE shouldBe true
|
||||
}
|
||||
|
||||
test("intermediate assignment steps have correct types for codegen phase (BeforeAsmGenerationAstChanger)") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte bb
|
||||
uword ww
|
||||
bb = not bb or not ww ; expression combining ubyte and uword
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
|
||||
|
||||
// bb = (( not bb as uword) or not ww)
|
||||
val bbAssign = result.program.entrypoint.statements.last() as Assignment
|
||||
val expr = bbAssign.value as BinaryExpression
|
||||
expr.operator shouldBe "or"
|
||||
expr.left shouldBe instanceOf<TypecastExpression>() // casted to word
|
||||
expr.right shouldBe instanceOf<PrefixExpression>()
|
||||
expr.left.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
expr.right.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
val options = CompilationOptions(OutputType.PRG, CbmPrgLauncherType.BASIC, ZeropageType.DONTUSE, emptyList(),
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
compTarget = C64Target(),
|
||||
loadAddress = 0u, outputDir= Helpers.outputDir)
|
||||
result.program.processAstBeforeAsmGeneration(options, ErrorReporterForTests())
|
||||
|
||||
// assignment is now split into:
|
||||
// bb = not bb
|
||||
// bb = (bb or not ww)
|
||||
|
||||
val assigns = result.program.entrypoint.statements.filterIsInstance<Assignment>()
|
||||
val bbAssigns = assigns.filter { it.value !is NumericLiteral }
|
||||
bbAssigns.size shouldBe 2
|
||||
|
||||
bbAssigns[0].target.identifier!!.nameInSource shouldBe listOf("bb")
|
||||
bbAssigns[0].value shouldBe instanceOf<PrefixExpression>()
|
||||
(bbAssigns[0].value as PrefixExpression).operator shouldBe "not"
|
||||
((bbAssigns[0].value as PrefixExpression).expression as? IdentifierReference)?.nameInSource shouldBe listOf("bb")
|
||||
bbAssigns[0].value.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
bbAssigns[1].target.identifier!!.nameInSource shouldBe listOf("bb")
|
||||
val bbAssigns1expr = bbAssigns[1].value as BinaryExpression
|
||||
bbAssigns1expr.operator shouldBe "or"
|
||||
(bbAssigns1expr.left as? IdentifierReference)?.nameInSource shouldBe listOf("bb")
|
||||
bbAssigns1expr.right shouldBe instanceOf<PrefixExpression>()
|
||||
(bbAssigns1expr.right as PrefixExpression).operator shouldBe "not"
|
||||
((bbAssigns1expr.right as PrefixExpression).expression as? IdentifierReference)?.nameInSource shouldBe listOf("ww")
|
||||
bbAssigns1expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
val asm = generateAssembly(result.program, options)
|
||||
asm shouldNotBe null
|
||||
asm!!.name.shouldNotBeBlank()
|
||||
val value1 = (stmts[4] as Assignment).value as BinaryExpression
|
||||
val value2 = (stmts[5] as Assignment).value as BinaryExpression
|
||||
val value3 = (stmts[6] as Assignment).value as BinaryExpression
|
||||
value1.operator shouldBe "=="
|
||||
value1.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
value2.operator shouldBe "|"
|
||||
value3.operator shouldBe "&"
|
||||
}
|
||||
|
||||
test("intermediate assignment steps generated for typecasted expression") {
|
||||
@@ -453,6 +400,7 @@ class TestOptimization: FunSpec({
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
||||
printProgram(result.program)
|
||||
/* expected:
|
||||
ubyte z1
|
||||
z1 = 10
|
||||
@@ -689,6 +637,7 @@ class TestOptimization: FunSpec({
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), optimize=true, src, writeAssembly=false)!!
|
||||
printProgram(result.program)
|
||||
/*
|
||||
expected result:
|
||||
ubyte[] auto_heap_var = [1,4,99,3]
|
||||
|
||||
@@ -1,17 +1,690 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.ints.shouldBeGreaterThan
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
class TestTypecasts: FunSpec({
|
||||
|
||||
test("integer args for builtin funcs") {
|
||||
val text="""
|
||||
%import floats
|
||||
main {
|
||||
sub start() {
|
||||
float fl
|
||||
floats.print_f(abs(fl))
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = compileText(VMTarget(), false, text, writeAssembly = false, errors=errors)
|
||||
result shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "type mismatch, was: FLOAT expected one of: [UBYTE, BYTE, UWORD, WORD]"
|
||||
}
|
||||
|
||||
test("not casting bool operands to logical operators") {
|
||||
val text="""
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
bool bb2=true
|
||||
bool @shared bb = bb2 and true
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 4
|
||||
val expr = (stmts[3] as Assignment).value as BinaryExpression
|
||||
expr.operator shouldBe "and"
|
||||
expr.right shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
|
||||
(expr.left as IdentifierReference).nameInSource shouldBe listOf("bb2") // no cast
|
||||
|
||||
val result2 = compileText(C64Target(), true, text, writeAssembly = true)!!
|
||||
val stmts2 = result2.program.entrypoint.statements
|
||||
stmts2.size shouldBe 6
|
||||
val expr2 = (stmts2[4] as Assignment).value as BinaryExpression
|
||||
expr2.operator shouldBe "&"
|
||||
expr2.right shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
|
||||
(expr2.left as IdentifierReference).nameInSource shouldBe listOf("bb")
|
||||
}
|
||||
|
||||
test("bool expressions with functioncalls") {
|
||||
val text="""
|
||||
main {
|
||||
sub ftrue(ubyte arg) -> ubyte {
|
||||
arg++
|
||||
return 64
|
||||
}
|
||||
|
||||
sub start() {
|
||||
bool ub1 = true
|
||||
bool ub2 = true
|
||||
bool ub3 = true
|
||||
bool ub4 = 0
|
||||
bool @shared bvalue
|
||||
|
||||
bvalue = ub1 xor ub2 xor ub3 xor true
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ftrue(99)
|
||||
bvalue = ub1 and ub2 and ftrue(99)
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
/*
|
||||
ubyte ub1
|
||||
ub1 = 1
|
||||
ubyte ub2
|
||||
ub2 = 1
|
||||
ubyte ub3
|
||||
ub3 = 1
|
||||
ubyte @shared bvalue
|
||||
bvalue = (((ub1^ub2)^ub3)^1)
|
||||
bvalue = (((ub1^ub2)^ub3)^(ftrue(99)!=0))
|
||||
bvalue = ((ub1&ub2)&(ftrue(99)!=0))
|
||||
return
|
||||
*/
|
||||
stmts.size shouldBe 11
|
||||
val assignValue1 = (stmts[7] as Assignment).value as BinaryExpression
|
||||
val assignValue2 = (stmts[8] as Assignment).value as BinaryExpression
|
||||
val assignValue3 = (stmts[9] as Assignment).value as BinaryExpression
|
||||
assignValue1.operator shouldBe "^"
|
||||
assignValue2.operator shouldBe "^"
|
||||
assignValue3.operator shouldBe "&"
|
||||
val right1 = assignValue1.right as NumericLiteral
|
||||
val right2 = assignValue2.right as BinaryExpression
|
||||
val right3 = assignValue3.right as BinaryExpression
|
||||
right1.number shouldBe 1.0
|
||||
right2.operator shouldBe "!="
|
||||
right3.operator shouldBe "!="
|
||||
right2.left shouldBe instanceOf<IFunctionCall>()
|
||||
right2.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
right3.left shouldBe instanceOf<IFunctionCall>()
|
||||
right3.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("logical with byte instead of bool") {
|
||||
val text="""
|
||||
%import textio
|
||||
|
||||
main {
|
||||
|
||||
sub ftrue(ubyte arg) -> ubyte {
|
||||
arg++
|
||||
return 128
|
||||
}
|
||||
|
||||
sub ffalse(ubyte arg) -> ubyte {
|
||||
arg++
|
||||
return 0
|
||||
}
|
||||
|
||||
sub start() {
|
||||
ubyte ub1 = 2
|
||||
ubyte ub2 = 4
|
||||
ubyte ub3 = 8
|
||||
ubyte ub4 = 0
|
||||
ubyte bvalue
|
||||
|
||||
txt.print("const not 0: ")
|
||||
txt.print_ub(not 129)
|
||||
txt.nl()
|
||||
txt.print("const not 1: ")
|
||||
txt.print_ub(not 0)
|
||||
txt.nl()
|
||||
txt.print("const inv 126: ")
|
||||
txt.print_ub(~ 129)
|
||||
txt.nl()
|
||||
txt.print("const inv 255: ")
|
||||
txt.print_ub(~ 0)
|
||||
txt.nl()
|
||||
bvalue = 129
|
||||
txt.print("bitwise inv 126: ")
|
||||
bvalue = ~ bvalue
|
||||
txt.print_ub(bvalue)
|
||||
txt.nl()
|
||||
bvalue = 0
|
||||
txt.print("bitwise inv 255: ")
|
||||
bvalue = ~ bvalue
|
||||
txt.print_ub(bvalue)
|
||||
txt.nl()
|
||||
|
||||
txt.print("bitwise or 14: ")
|
||||
txt.print_ub(ub1 | ub2 | ub3 | ub4)
|
||||
txt.nl()
|
||||
txt.print("bitwise or 142: ")
|
||||
txt.print_ub(ub1 | ub2 | ub3 | ub4 | 128)
|
||||
txt.nl()
|
||||
txt.print("bitwise and 0: ")
|
||||
txt.print_ub(ub1 & ub2 & ub3 & ub4)
|
||||
txt.nl()
|
||||
txt.print("bitwise and 8: ")
|
||||
txt.print_ub(ub3 & ub3 & 127)
|
||||
txt.nl()
|
||||
txt.print("bitwise xor 14: ")
|
||||
txt.print_ub(ub1 ^ ub2 ^ ub3 ^ ub4)
|
||||
txt.nl()
|
||||
txt.print("bitwise xor 6: ")
|
||||
txt.print_ub(ub1 ^ ub2 ^ ub3 ^ 8)
|
||||
txt.nl()
|
||||
txt.print("bitwise not 247: ")
|
||||
txt.print_ub(~ub3)
|
||||
txt.nl()
|
||||
txt.print("bitwise not 255: ")
|
||||
txt.print_ub(~ub4)
|
||||
txt.nl()
|
||||
|
||||
txt.print("not 0: ")
|
||||
bvalue = 3 * (ub4 | not (ub3 | ub3 | ub3))
|
||||
txt.print_ub(bvalue)
|
||||
if 3*(ub4 | not (ub1 | ub1 | ub1))
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
|
||||
txt.print("not 0: ")
|
||||
bvalue = not ub3
|
||||
txt.print_ub(bvalue)
|
||||
if not ub1
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
|
||||
txt.print("not 1: ")
|
||||
bvalue = not ub4
|
||||
txt.print_ub(bvalue)
|
||||
if not ub4
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
bvalue = bvalue and 128
|
||||
txt.print("bvl 1: ")
|
||||
txt.print_ub(bvalue)
|
||||
if bvalue and 128
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
txt.print("and 1: ")
|
||||
bvalue = ub1 and ub2 and ub3
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("and 1: ")
|
||||
bvalue = ub1 and ub2 and ub3 and 64
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and 64
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("and 1: ")
|
||||
bvalue = ub1 and ub2 and ub3 and ftrue(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and ftrue(99)
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("and 0: ")
|
||||
bvalue = ub1 and ub2 and ub3 and ub4
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and ub4
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
txt.print("and 0: ")
|
||||
bvalue = ub1 and ub2 and ub3 and ffalse(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and ffalse(99)
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" or 1: ")
|
||||
bvalue = ub1 or ub2 or ub3 or ub4
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 or ub2 or ub3 or ub4
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print(" or 1: ")
|
||||
bvalue = ub4 or ub4 or ub1
|
||||
txt.print_ub(bvalue)
|
||||
if ub4 or ub4 or ub1
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print(" or 1: ")
|
||||
bvalue = ub1 or ub2 or ub3 or ftrue(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 or ub2 or ub3 or ftrue(99)
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
txt.print("xor 1: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ub4
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ub4
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("xor 1: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ffalse(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ffalse(99)
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
txt.print("xor 0: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ub4 xor true
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ub4 xor true
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
txt.print("xor 0: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ftrue(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ftrue(99)
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBeGreaterThan 10
|
||||
}
|
||||
|
||||
test("logical with bools") {
|
||||
val text="""
|
||||
%import textio
|
||||
|
||||
main {
|
||||
|
||||
sub ftrue(ubyte arg) -> ubyte {
|
||||
arg++
|
||||
return 128
|
||||
}
|
||||
|
||||
sub ffalse(ubyte arg) -> ubyte {
|
||||
arg++
|
||||
return 0
|
||||
}
|
||||
|
||||
sub start() {
|
||||
bool ub1 = 2
|
||||
bool ub2 = 4
|
||||
bool ub3 = 8
|
||||
bool ub4 = 0
|
||||
bool bvalue
|
||||
|
||||
txt.print("const not 0: ")
|
||||
txt.print_ub(not 129)
|
||||
txt.nl()
|
||||
txt.print("const not 1: ")
|
||||
txt.print_ub(not 0)
|
||||
txt.nl()
|
||||
txt.print("const inv 126: ")
|
||||
txt.print_ub(~ 129)
|
||||
txt.nl()
|
||||
txt.print("const inv 255: ")
|
||||
txt.print_ub(~ 0)
|
||||
txt.nl()
|
||||
bvalue = 129
|
||||
txt.print("bitwise inv 126: ")
|
||||
bvalue = ~ bvalue
|
||||
txt.print_ub(bvalue)
|
||||
txt.nl()
|
||||
bvalue = 0
|
||||
txt.print("bitwise inv 255: ")
|
||||
bvalue = ~ bvalue
|
||||
txt.print_ub(bvalue)
|
||||
txt.nl()
|
||||
|
||||
txt.print("bitwise or 14: ")
|
||||
txt.print_ub(ub1 | ub2 | ub3 | ub4)
|
||||
txt.nl()
|
||||
txt.print("bitwise or 142: ")
|
||||
txt.print_ub(ub1 | ub2 | ub3 | ub4 | 128)
|
||||
txt.nl()
|
||||
txt.print("bitwise and 0: ")
|
||||
txt.print_ub(ub1 & ub2 & ub3 & ub4)
|
||||
txt.nl()
|
||||
txt.print("bitwise and 8: ")
|
||||
txt.print_ub(ub3 & ub3 & 127)
|
||||
txt.nl()
|
||||
txt.print("bitwise xor 14: ")
|
||||
txt.print_ub(ub1 ^ ub2 ^ ub3 ^ ub4)
|
||||
txt.nl()
|
||||
txt.print("bitwise xor 6: ")
|
||||
txt.print_ub(ub1 ^ ub2 ^ ub3 ^ 8)
|
||||
txt.nl()
|
||||
txt.print("bitwise not 247: ")
|
||||
txt.print_ub(~ub3)
|
||||
txt.nl()
|
||||
txt.print("bitwise not 255: ")
|
||||
txt.print_ub(~ub4)
|
||||
txt.nl()
|
||||
|
||||
txt.print("not 0: ")
|
||||
bvalue = 3 * (ub4 | not (ub3 | ub3 | ub3))
|
||||
txt.print_ub(bvalue)
|
||||
if 3*(ub4 | not (ub1 | ub1 | ub1))
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
|
||||
txt.print("not 0: ")
|
||||
bvalue = not ub3
|
||||
txt.print_ub(bvalue)
|
||||
if not ub1
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
|
||||
txt.print("not 1: ")
|
||||
bvalue = not ub4
|
||||
txt.print_ub(bvalue)
|
||||
if not ub4
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
bvalue = bvalue and 128
|
||||
txt.print("bvl 1: ")
|
||||
txt.print_ub(bvalue)
|
||||
if bvalue and 128
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
txt.print("and 1: ")
|
||||
bvalue = ub1 and ub2 and ub3
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("and 1: ")
|
||||
bvalue = ub1 and ub2 and ub3 and 64
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and 64
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("and 1: ")
|
||||
bvalue = ub1 and ub2 and ub3 and ftrue(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and ftrue(99)
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("and 0: ")
|
||||
bvalue = ub1 and ub2 and ub3 and ub4
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and ub4
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
txt.print("and 0: ")
|
||||
bvalue = ub1 and ub2 and ub3 and ffalse(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and ffalse(99)
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" or 1: ")
|
||||
bvalue = ub1 or ub2 or ub3 or ub4
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 or ub2 or ub3 or ub4
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print(" or 1: ")
|
||||
bvalue = ub4 or ub4 or ub1
|
||||
txt.print_ub(bvalue)
|
||||
if ub4 or ub4 or ub1
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print(" or 1: ")
|
||||
bvalue = ub1 or ub2 or ub3 or ftrue(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 or ub2 or ub3 or ftrue(99)
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
txt.print("xor 1: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ub4
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ub4
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("xor 1: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ffalse(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ffalse(99)
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
txt.print("xor 0: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ub4 xor true
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ub4 xor true
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
txt.print("xor 0: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ftrue(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ftrue(99)
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBeGreaterThan 10
|
||||
}
|
||||
|
||||
test("bool arrays") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
bool[] barray = [true, false, 1, 0, 222]
|
||||
bool bb
|
||||
ubyte xx
|
||||
|
||||
for bb in barray {
|
||||
if bb
|
||||
xx++
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 6
|
||||
val arraydecl = stmts[0] as VarDecl
|
||||
arraydecl.datatype shouldBe DataType.ARRAY_BOOL
|
||||
val values = (arraydecl.value as ArrayLiteral).value
|
||||
values.size shouldBe 5
|
||||
values[0] shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
|
||||
values[1] shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
values[2] shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
|
||||
values[3] shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
values[4] shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("correct handling of bool parameters") {
|
||||
val text="""
|
||||
main {
|
||||
|
||||
sub thing(bool b1, bool b2) -> bool {
|
||||
return (b1 and b2) or b1
|
||||
}
|
||||
|
||||
sub start() {
|
||||
bool boolvalue1 = true
|
||||
bool boolvalue2 = false
|
||||
uword xx
|
||||
|
||||
boolvalue1 = thing(true, false)
|
||||
boolvalue2 = thing(xx, xx)
|
||||
|
||||
if boolvalue1 and boolvalue2
|
||||
boolvalue1=false
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 9
|
||||
val fcall1 = ((stmts[6] as Assignment).value as IFunctionCall)
|
||||
fcall1.args[0] shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
|
||||
fcall1.args[1] shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
val fcall2 = ((stmts[7] as Assignment).value as IFunctionCall)
|
||||
(fcall2.args[0] as TypecastExpression).type shouldBe DataType.BOOL
|
||||
(fcall2.args[1] as TypecastExpression).type shouldBe DataType.BOOL
|
||||
val ifCond = (stmts[8] as IfElse).condition as BinaryExpression
|
||||
ifCond.operator shouldBe "and" // no asm writing so logical expressions haven't been replaced with bitwise equivalents yet
|
||||
(ifCond.left as IdentifierReference).nameInSource shouldBe listOf("boolvalue1")
|
||||
(ifCond.right as IdentifierReference).nameInSource shouldBe listOf("boolvalue2")
|
||||
}
|
||||
|
||||
test("correct evaluation of words in boolean expressions") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
uword camg
|
||||
ubyte @shared interlaced
|
||||
interlaced = (camg & ${'$'}0004) != 0
|
||||
interlaced++
|
||||
interlaced = (${'$'}0004 & camg) != 0
|
||||
interlaced++
|
||||
uword @shared ww
|
||||
ww = (camg & ${'$'}0004)
|
||||
ww++
|
||||
ww = (${'$'}0004 & camg)
|
||||
|
||||
ubyte @shared wordNr2 = (interlaced >= ${'$'}33) + (interlaced >= ${'$'}66) + (interlaced >= ${'$'}99) + (interlaced >= ${'$'}CC)
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, text, writeAssembly = true)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 14
|
||||
}
|
||||
|
||||
test("word to byte casts") {
|
||||
val text="""
|
||||
%import textio
|
||||
main {
|
||||
sub func(ubyte arg) -> word {
|
||||
return arg-99
|
||||
}
|
||||
|
||||
sub start() {
|
||||
txt.print_ub(func(0) as ubyte)
|
||||
txt.print_uw(func(0) as ubyte)
|
||||
txt.print_w(func(0) as ubyte)
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 3
|
||||
}
|
||||
|
||||
test("add missing & to function arguments") {
|
||||
val text="""
|
||||
main {
|
||||
|
||||
sub handler(uword fptr) {
|
||||
}
|
||||
|
||||
sub start() {
|
||||
uword variable
|
||||
|
||||
pushw(variable)
|
||||
pushw(handler)
|
||||
pushw(&handler)
|
||||
handler(variable)
|
||||
handler(handler)
|
||||
handler(&handler)
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = false)!!
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 8
|
||||
val arg1 = (stmts[2] as IFunctionCall).args.single()
|
||||
val arg2 = (stmts[3] as IFunctionCall).args.single()
|
||||
val arg3 = (stmts[4] as IFunctionCall).args.single()
|
||||
val arg4 = (stmts[5] as IFunctionCall).args.single()
|
||||
val arg5 = (stmts[6] as IFunctionCall).args.single()
|
||||
val arg6 = (stmts[7] as IFunctionCall).args.single()
|
||||
arg1 shouldBe instanceOf<IdentifierReference>()
|
||||
arg2 shouldBe instanceOf<AddressOf>()
|
||||
arg3 shouldBe instanceOf<AddressOf>()
|
||||
arg4 shouldBe instanceOf<IdentifierReference>()
|
||||
arg5 shouldBe instanceOf<AddressOf>()
|
||||
arg6 shouldBe instanceOf<AddressOf>()
|
||||
}
|
||||
|
||||
test("correct typecasts") {
|
||||
val text = """
|
||||
%import floats
|
||||
@@ -152,7 +825,6 @@ class TestTypecasts: FunSpec({
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
|
||||
printProgram(result.program)
|
||||
val statements = result.program.entrypoint.statements
|
||||
statements.size shouldBe 27
|
||||
}
|
||||
@@ -181,7 +853,7 @@ class TestTypecasts: FunSpec({
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
|
||||
val statements = result.program.entrypoint.statements
|
||||
statements.size shouldBe 14
|
||||
statements.size shouldBeGreaterThan 10
|
||||
}
|
||||
|
||||
test("no infinite typecast loop in assignment asmgen") {
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub ftrue(ubyte arg) -> ubyte {
|
||||
arg++
|
||||
return 128
|
||||
}
|
||||
|
||||
sub ffalse(ubyte arg) -> ubyte {
|
||||
arg++
|
||||
return 0
|
||||
}
|
||||
|
||||
sub start() {
|
||||
ubyte ub1 = 2
|
||||
ubyte ub2 = 4
|
||||
ubyte ub3 = 8
|
||||
ubyte ub4 = 0
|
||||
ubyte bvalue
|
||||
|
||||
txt.print("const not 0: ")
|
||||
txt.print_ub(not 129)
|
||||
txt.nl()
|
||||
txt.print("const not 1: ")
|
||||
txt.print_ub(not 0)
|
||||
txt.nl()
|
||||
txt.print("const inv 126: ")
|
||||
txt.print_ub(~ 129)
|
||||
txt.nl()
|
||||
txt.print("const inv 255: ")
|
||||
txt.print_ub(~ 0)
|
||||
txt.nl()
|
||||
bvalue = 129
|
||||
txt.print("bitwise inv 126: ")
|
||||
bvalue = ~ bvalue
|
||||
txt.print_ub(bvalue)
|
||||
txt.nl()
|
||||
bvalue = 0
|
||||
txt.print("bitwise inv 255: ")
|
||||
bvalue = ~ bvalue
|
||||
txt.print_ub(bvalue)
|
||||
txt.nl()
|
||||
|
||||
txt.print("bitwise or 14: ")
|
||||
txt.print_ub(ub1 | ub2 | ub3 | ub4)
|
||||
txt.nl()
|
||||
txt.print("bitwise or 142: ")
|
||||
txt.print_ub(ub1 | ub2 | ub3 | ub4 | 128)
|
||||
txt.nl()
|
||||
txt.print("bitwise and 0: ")
|
||||
txt.print_ub(ub1 & ub2 & ub3 & ub4)
|
||||
txt.nl()
|
||||
txt.print("bitwise and 8: ")
|
||||
txt.print_ub(ub3 & ub3 & 127)
|
||||
txt.nl()
|
||||
txt.print("bitwise xor 14: ")
|
||||
txt.print_ub(ub1 ^ ub2 ^ ub3 ^ ub4)
|
||||
txt.nl()
|
||||
txt.print("bitwise xor 6: ")
|
||||
txt.print_ub(ub1 ^ ub2 ^ ub3 ^ 8)
|
||||
txt.nl()
|
||||
txt.print("bitwise not 247: ")
|
||||
txt.print_ub(~ub3)
|
||||
txt.nl()
|
||||
txt.print("bitwise not 255: ")
|
||||
txt.print_ub(~ub4)
|
||||
txt.nl()
|
||||
|
||||
txt.print("not 0: ")
|
||||
bvalue = 3 * (ub4 | not (ub3 | ub3 | ub3))
|
||||
txt.print_ub(bvalue)
|
||||
if 3*(ub4 | not (ub1 | ub1 | ub1))
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
|
||||
txt.print("not 0: ")
|
||||
bvalue = not ub3
|
||||
txt.print_ub(bvalue)
|
||||
if not ub1
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
|
||||
txt.print("not 1: ")
|
||||
bvalue = not ub4
|
||||
txt.print_ub(bvalue)
|
||||
if not ub4
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
bvalue = bvalue and 128
|
||||
txt.print("bvl 1: ")
|
||||
txt.print_ub(bvalue)
|
||||
if bvalue and 128
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
txt.print("and 1: ")
|
||||
bvalue = ub1 and ub2 and ub3
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("and 1: ")
|
||||
bvalue = ub1 and ub2 and ub3 and 64
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and 64
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("and 1: ")
|
||||
bvalue = ub1 and ub2 and ub3 and ftrue(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and ftrue(99)
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("and 0: ")
|
||||
bvalue = ub1 and ub2 and ub3 and ub4
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and ub4
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
txt.print("and 0: ")
|
||||
bvalue = ub1 and ub2 and ub3 and ffalse(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 and ub2 and ub3 and ffalse(99)
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" or 1: ")
|
||||
bvalue = ub1 or ub2 or ub3 or ub4
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 or ub2 or ub3 or ub4
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print(" or 1: ")
|
||||
bvalue = ub4 or ub4 or ub1
|
||||
txt.print_ub(bvalue)
|
||||
if ub4 or ub4 or ub1
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print(" or 1: ")
|
||||
bvalue = ub1 or ub2 or ub3 or ftrue(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 or ub2 or ub3 or ftrue(99)
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
txt.print("xor 1: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ub4
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ub4
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
txt.print("xor 1: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ffalse(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ffalse(99)
|
||||
txt.print(" / ok")
|
||||
else
|
||||
txt.print(" / fail")
|
||||
txt.nl()
|
||||
|
||||
txt.print("xor 0: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ub4 xor true
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ub4 xor true
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
txt.nl()
|
||||
txt.print("xor 0: ")
|
||||
bvalue = ub1 xor ub2 xor ub3 xor ftrue(99)
|
||||
txt.print_ub(bvalue)
|
||||
if ub1 xor ub2 xor ub3 xor ftrue(99)
|
||||
txt.print(" / fail")
|
||||
else
|
||||
txt.print(" / ok")
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@ class TestIntermediateAst: FunSpec({
|
||||
arraydecl.name shouldBe "array"
|
||||
arraydecl.type shouldBe DataType.ARRAY_UB
|
||||
|
||||
val containment = (entry.children[2] as PtAssignment).value as PtContainmentCheck
|
||||
val containmentCast = (entry.children[2] as PtAssignment).value as PtTypeCast
|
||||
containmentCast.type shouldBe DataType.UBYTE
|
||||
val containment = containmentCast.value as PtContainmentCheck
|
||||
(containment.element as PtNumber).number shouldBe 11.0
|
||||
val fcall = (entry.children[3] as PtAssignment).value as PtFunctionCall
|
||||
fcall.void shouldBe false
|
||||
|
||||
@@ -173,8 +173,8 @@ class TestProg8Parser: FunSpec( {
|
||||
|
||||
context("ImportDirectives") {
|
||||
test("should not be looked into by the parser") {
|
||||
val importedNoExt = Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist")
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir, "i_do_not_exist.p8")
|
||||
val importedNoExt = assumeNotExists(fixturesDir, "i_do_not_exist")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.p8")
|
||||
val text = "%import ${importedNoExt.name}"
|
||||
val module = parseModule(SourceCode.Text(text))
|
||||
|
||||
@@ -189,7 +189,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("from an empty file should result in empty Module") {
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_empty.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_empty.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
module.statements.size shouldBe 0
|
||||
}
|
||||
@@ -208,7 +208,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("parsed from a file") {
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
module.name shouldBe path.nameWithoutExtension
|
||||
}
|
||||
@@ -271,7 +271,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("in ParseError from bad file source code") {
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
val e = shouldThrow<ParseError> { parseModule(SourceCode.File(path)) }
|
||||
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6)
|
||||
@@ -287,13 +287,13 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("of Module parsed from a file") {
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
|
||||
}
|
||||
|
||||
test("of non-root Nodes parsed from file") {
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
val mpf = module.position.file
|
||||
@@ -357,7 +357,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("isn't absolute for filesystem paths") {
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertSomethingForAllNodes(module) {
|
||||
Path(it.position.file).isAbsolute shouldBe false
|
||||
@@ -743,7 +743,7 @@ class TestProg8Parser: FunSpec( {
|
||||
main {
|
||||
ubyte bb
|
||||
uword ww
|
||||
ubyte bb2 = not bb or not ww ; expression combining ubyte and uword
|
||||
bool bb2 = (3+bb) or (3333+ww) ; expression combining ubyte and uword
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
@@ -755,7 +755,7 @@ class TestProg8Parser: FunSpec( {
|
||||
expr.operator shouldBe "or"
|
||||
expr.left.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
expr.right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
expr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
expr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.BOOL
|
||||
}
|
||||
|
||||
test("inferred type for typecasted expressions with logical operators") {
|
||||
@@ -779,15 +779,15 @@ class TestProg8Parser: FunSpec( {
|
||||
val bb2 = (stmts[4] as VarDecl).value as BinaryExpression
|
||||
val zz2 = (stmts[5] as VarDecl).value as BinaryExpression
|
||||
qq.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
zz.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
bb2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
zz.inferType(program).getOrElse { fail("dt") } shouldBe DataType.BOOL
|
||||
bb2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.BOOL
|
||||
|
||||
zz2.operator shouldBe "or"
|
||||
val left = zz2.left as TypecastExpression
|
||||
val right = zz2.right as PrefixExpression
|
||||
left.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
zz2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE // 'or' causes UBYTE result
|
||||
right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.BOOL
|
||||
zz2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.BOOL
|
||||
}
|
||||
|
||||
test("type cast from byte to ubyte as desired target type") {
|
||||
@@ -885,6 +885,7 @@ class TestProg8Parser: FunSpec( {
|
||||
str string = "hello"
|
||||
ubyte[] array = [1,2,3,4]
|
||||
|
||||
bool bb
|
||||
ubyte cc
|
||||
if cc in [' ', '@', 0] {
|
||||
}
|
||||
@@ -892,8 +893,8 @@ class TestProg8Parser: FunSpec( {
|
||||
if cc in "email" {
|
||||
}
|
||||
|
||||
cc = 99 in array
|
||||
cc = '@' in string
|
||||
bb = 99 in array
|
||||
bb = '@' in string
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,10 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
||||
import prog8tests.helpers.Helpers
|
||||
import prog8tests.helpers.assumeNotExists
|
||||
import prog8tests.helpers.assumeReadableFile
|
||||
import prog8tests.helpers.fixturesDir
|
||||
import prog8tests.helpers.resourcesDir
|
||||
import kotlin.io.path.Path
|
||||
|
||||
|
||||
@@ -29,26 +32,26 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromPathWithNonExistingPath() {
|
||||
val filename = "i_do_not_exist.p8"
|
||||
val path = Helpers.assumeNotExists(Helpers.fixturesDir, filename)
|
||||
val path = assumeNotExists(fixturesDir, filename)
|
||||
shouldThrow<NoSuchFileException> { SourceCode.File(path) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithMissingExtension_p8() {
|
||||
val pathWithoutExt = Helpers.assumeNotExists(Helpers.fixturesDir,"simple_main")
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir,"ast_simple_main.p8")
|
||||
val pathWithoutExt = assumeNotExists(fixturesDir,"simple_main")
|
||||
assumeReadableFile(fixturesDir,"ast_simple_main.p8")
|
||||
shouldThrow<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithDirectory() {
|
||||
shouldThrow<FileSystemException> { SourceCode.File(Helpers.fixturesDir) }
|
||||
shouldThrow<FileSystemException> { SourceCode.File(fixturesDir) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingPath() {
|
||||
val filename = "ast_simple_main.p8"
|
||||
val path = Helpers.assumeReadableFile(Helpers.fixturesDir, filename)
|
||||
val path = assumeReadableFile(fixturesDir, filename)
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
src.origin shouldBe expectedOrigin
|
||||
@@ -61,7 +64,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
fun testFromPathWithExistingNonNormalizedPath() {
|
||||
val filename = "ast_simple_main.p8"
|
||||
val path = Path(".", "test", "..", "test", "fixtures", filename)
|
||||
val srcFile = Helpers.assumeReadableFile(path).toFile()
|
||||
val srcFile = assumeReadableFile(path).toFile()
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
src.origin shouldBe expectedOrigin
|
||||
@@ -71,7 +74,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithExistingP8File_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/math.p8"
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString).toFile()
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix/$pathString"
|
||||
@@ -83,7 +86,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithExistingP8File_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/math.p8"
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString.substring(1)).toFile()
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix$pathString"
|
||||
@@ -93,7 +96,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithExistingAsmFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/math.asm"
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString).toFile()
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix/$pathString"
|
||||
@@ -104,7 +107,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithExistingAsmFile_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/math.asm"
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString.substring(1)).toFile()
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix$pathString"
|
||||
@@ -114,7 +117,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithNonNormalizedPath() {
|
||||
val pathString = "/prog8lib/../prog8lib/math.p8"
|
||||
val srcFile = Helpers.assumeReadableFile(Helpers.resourcesDir, pathString.substring(1)).toFile()
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$libraryFilePrefix/prog8lib/math.p8"
|
||||
@@ -126,14 +129,14 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromResourcesWithNonExistingFile_withLeadingSlash() {
|
||||
val pathString = "/prog8lib/i_do_not_exist"
|
||||
Helpers.assumeNotExists(Helpers.resourcesDir, pathString.substring(1))
|
||||
assumeNotExists(resourcesDir, pathString.substring(1))
|
||||
|
||||
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
@Test
|
||||
fun testFromResourcesWithNonExistingFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/i_do_not_exist"
|
||||
Helpers.assumeNotExists(Helpers.resourcesDir, pathString)
|
||||
assumeNotExists(resourcesDir, pathString)
|
||||
|
||||
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package prog8tests.codegeneration
|
||||
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.AddressOf
|
||||
@@ -13,10 +14,7 @@ import prog8.code.target.C64Target
|
||||
import prog8.code.target.c64.C64Zeropage
|
||||
import prog8.codegen.cpu6502.AsmGen
|
||||
import prog8.compiler.astprocessing.SymbolTableMaker
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.*
|
||||
|
||||
class TestAsmGenSymbols: StringSpec({
|
||||
fun createTestProgram(): Program {
|
||||
@@ -153,4 +151,26 @@ main {
|
||||
asmgen.asmSymbolName(id1) shouldBe "P8ZP_SCRATCH_REG"
|
||||
asmgen.asmSymbolName(id2) shouldBe "P8ZP_SCRATCH_W2"
|
||||
}
|
||||
|
||||
"no double labels with various loops" {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
if true {
|
||||
}
|
||||
|
||||
repeat 4 {
|
||||
}
|
||||
|
||||
while true {
|
||||
}
|
||||
|
||||
repeat {
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)
|
||||
result shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -19,13 +19,13 @@ internal fun compileFile(
|
||||
optimize: Boolean,
|
||||
fileDir: Path,
|
||||
fileName: String,
|
||||
outputDir: Path = prog8tests.helpers.Helpers.outputDir,
|
||||
outputDir: Path = prog8tests.helpers.outputDir,
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true,
|
||||
optFloatExpr: Boolean = true
|
||||
) : CompilationResult? {
|
||||
val filepath = fileDir.resolve(fileName)
|
||||
Helpers.assumeReadableFile(filepath)
|
||||
assumeReadableFile(filepath)
|
||||
val args = CompilerArguments(
|
||||
filepath,
|
||||
optimize,
|
||||
@@ -37,6 +37,8 @@ internal fun compileFile(
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
platform.name,
|
||||
evalStackBaseAddress = null,
|
||||
symbolDefs = emptyMap(),
|
||||
outputDir = outputDir,
|
||||
errors = errors ?: ErrorReporterForTests()
|
||||
)
|
||||
@@ -56,7 +58,7 @@ internal fun compileText(
|
||||
writeAssembly: Boolean = true,
|
||||
optFloatExpr: Boolean = true
|
||||
) : CompilationResult? {
|
||||
val filePath = Helpers.outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
|
||||
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
|
||||
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
|
||||
filePath.toFile().writeText(sourceText)
|
||||
return compileFile(platform, optimize, filePath.parent, filePath.name, errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr)
|
||||
@@ -71,7 +73,7 @@ internal fun generateAssembly(
|
||||
floats = true,
|
||||
noSysInit = true,
|
||||
compTarget = C64Target(),
|
||||
loadAddress = 0u, outputDir = Helpers.outputDir)
|
||||
loadAddress = 0u, outputDir = outputDir)
|
||||
coptions.compTarget.machine.zeropage = C64Zeropage(coptions)
|
||||
val st = SymbolTableMaker().makeFrom(program)
|
||||
val errors = ErrorReporterForTests()
|
||||
|
||||
@@ -1,68 +1,66 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
object Combinations {
|
||||
fun <T, U> cartesianProduct(c1: Collection<T>, c2: Collection<U>): Sequence<Pair<T, U>> {
|
||||
return c1.flatMap { lhsElem -> c2.map { rhsElem -> lhsElem to rhsElem } }.asSequence()
|
||||
fun <T, U> cartesianProduct(c1: Collection<T>, c2: Collection<U>): Sequence<Pair<T, U>> {
|
||||
return c1.flatMap { lhsElem -> c2.map { rhsElem -> lhsElem to rhsElem } }.asSequence()
|
||||
}
|
||||
|
||||
fun <T, U, V> cartesianProduct(c1: Collection<T>, c2: Collection<U>, c3: Collection<V>): Sequence<Triple<T, U, V>> {
|
||||
return sequence {
|
||||
for (a in c1)
|
||||
for (b in c2)
|
||||
for (c in c3)
|
||||
yield(Triple(a, b, c))
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, U, V> cartesianProduct(c1: Collection<T>, c2: Collection<U>, c3: Collection<V>): Sequence<Triple<T, U, V>> {
|
||||
return sequence {
|
||||
for (a in c1)
|
||||
for (b in c2)
|
||||
for (c in c3)
|
||||
yield(Triple(a, b, c))
|
||||
}
|
||||
data class Product<out T, out U, out V, out W>(val first: T, val second: U, val third: V, val fourth: W)
|
||||
|
||||
fun <T, U, V, W> cartesianProduct(
|
||||
c1: Collection<T>,
|
||||
c2: Collection<U>,
|
||||
c3: Collection<V>,
|
||||
c4: Collection<W>
|
||||
): Sequence<Product<T, U, V, W>> {
|
||||
return sequence {
|
||||
for (a in c1)
|
||||
for (b in c2)
|
||||
for (c in c3)
|
||||
for (d in c4)
|
||||
yield(Product(a, b, c, d))
|
||||
}
|
||||
}
|
||||
|
||||
data class Product<out T, out U, out V, out W>(val first: T, val second: U, val third: V, val fourth: W)
|
||||
fun <A, B, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, combine2: (A, B) -> R) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
yield(combine2(a, b))
|
||||
}.toList()
|
||||
|
||||
fun <T, U, V, W> cartesianProduct(
|
||||
c1: Collection<T>,
|
||||
c2: Collection<U>,
|
||||
c3: Collection<V>,
|
||||
c4: Collection<W>
|
||||
): Sequence<Product<T, U, V, W>> {
|
||||
return sequence {
|
||||
for (a in c1)
|
||||
for (b in c2)
|
||||
for (c in c3)
|
||||
for (d in c4)
|
||||
yield(Product(a, b, c, d))
|
||||
}
|
||||
}
|
||||
fun <A, B, C, R> mapCombinations(
|
||||
dim1: Iterable<A>,
|
||||
dim2: Iterable<B>,
|
||||
dim3: Iterable<C>,
|
||||
combine3: (A, B, C) -> R
|
||||
) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
yield(combine3(a, b, c))
|
||||
}.toList()
|
||||
|
||||
fun <A, B, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, combine2: (A, B) -> R) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
yield(combine2(a, b))
|
||||
}.toList()
|
||||
|
||||
fun <A, B, C, R> mapCombinations(
|
||||
dim1: Iterable<A>,
|
||||
dim2: Iterable<B>,
|
||||
dim3: Iterable<C>,
|
||||
combine3: (A, B, C) -> R
|
||||
) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
yield(combine3(a, b, c))
|
||||
}.toList()
|
||||
|
||||
fun <A, B, C, D, R> mapCombinations(
|
||||
dim1: Iterable<A>,
|
||||
dim2: Iterable<B>,
|
||||
dim3: Iterable<C>,
|
||||
dim4: Iterable<D>,
|
||||
combine4: (A, B, C, D) -> R
|
||||
) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
for (d in dim4)
|
||||
yield(combine4(a, b, c, d))
|
||||
}.toList()
|
||||
}
|
||||
fun <A, B, C, D, R> mapCombinations(
|
||||
dim1: Iterable<A>,
|
||||
dim2: Iterable<B>,
|
||||
dim3: Iterable<C>,
|
||||
dim4: Iterable<D>,
|
||||
combine4: (A, B, C, D) -> R
|
||||
) =
|
||||
sequence {
|
||||
for (a in dim1)
|
||||
for (b in dim2)
|
||||
for (c in dim3)
|
||||
for (d in dim4)
|
||||
yield(combine4(a, b, c, d))
|
||||
}.toList()
|
||||
@@ -7,69 +7,66 @@ import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
|
||||
object Helpers {
|
||||
val workingDir = assumeDirectory("").absolute() // Note: "." does NOT work..!
|
||||
val fixturesDir = assumeDirectory(workingDir, "test/fixtures")
|
||||
val resourcesDir = assumeDirectory(workingDir, "res")
|
||||
val outputDir: Path =
|
||||
createIfNotExists(workingDir, "build/tmp/test").also { assumeDirectory(workingDir, "build/tmp/test") }
|
||||
val workingDir = assumeDirectory("").absolute() // Note: "." does NOT work..!
|
||||
val fixturesDir = assumeDirectory(workingDir, "test/fixtures")
|
||||
val resourcesDir = assumeDirectory(workingDir, "res")
|
||||
val outputDir: Path =
|
||||
createIfNotExists(workingDir, "build/tmp/test").also { assumeDirectory(workingDir, "build/tmp/test") }
|
||||
|
||||
fun createIfNotExists(workingDir: Path, path: String): Path {
|
||||
val dir = workingDir / path
|
||||
if (!dir.toFile().isDirectory)
|
||||
Files.createDirectories(dir)
|
||||
return dir
|
||||
fun createIfNotExists(workingDir: Path, path: String): Path {
|
||||
val dir = workingDir / path
|
||||
if (!dir.toFile().isDirectory)
|
||||
Files.createDirectories(dir)
|
||||
return dir
|
||||
}
|
||||
|
||||
fun assumeNotExists(path: Path): Path {
|
||||
withClue("sanity check: should not exist: ${path.absolute()}") {
|
||||
path.exists() shouldBe false
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeNotExists(path: Path): Path {
|
||||
withClue("sanity check: should not exist: ${path.absolute()}") {
|
||||
path.exists() shouldBe false
|
||||
}
|
||||
return path
|
||||
fun assumeNotExists(pathStr: String): Path = assumeNotExists(Path(pathStr))
|
||||
fun assumeNotExists(path: Path, other: String): Path = assumeNotExists(path / other)
|
||||
|
||||
fun assumeReadable(path: Path): Path {
|
||||
withClue("sanity check: should be readable: ${path.absolute()}") {
|
||||
path.isReadable() shouldBe true
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeNotExists(pathStr: String): Path = assumeNotExists(Path(pathStr))
|
||||
fun assumeNotExists(path: Path, other: String): Path = assumeNotExists(path / other)
|
||||
|
||||
fun assumeReadable(path: Path): Path {
|
||||
withClue("sanity check: should be readable: ${path.absolute()}") {
|
||||
path.isReadable() shouldBe true
|
||||
}
|
||||
return path
|
||||
fun assumeReadableFile(path: Path): Path {
|
||||
withClue("sanity check: should be normal file: ${path.absolute()}") {
|
||||
path.isRegularFile() shouldBe true
|
||||
}
|
||||
return assumeReadable(path)
|
||||
}
|
||||
|
||||
fun assumeReadableFile(path: Path): Path {
|
||||
withClue("sanity check: should be normal file: ${path.absolute()}") {
|
||||
path.isRegularFile() shouldBe true
|
||||
}
|
||||
return assumeReadable(path)
|
||||
fun assumeReadableFile(pathStr: String): Path = assumeReadableFile(Path(pathStr))
|
||||
fun assumeReadableFile(pathStr: String, other: Path): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(pathStr: String, other: String): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(path: Path, other: String): Path = assumeReadableFile(path / other)
|
||||
fun assumeReadableFile(path: Path, other: Path): Path = assumeReadableFile(path / other)
|
||||
|
||||
fun assumeDirectory(path: Path): Path {
|
||||
withClue("sanity check; should be directory: $path") {
|
||||
path.isDirectory() shouldBe true
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeReadableFile(pathStr: String): Path = assumeReadableFile(Path(pathStr))
|
||||
fun assumeReadableFile(pathStr: String, other: Path): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(pathStr: String, other: String): Path = assumeReadableFile(Path(pathStr), other)
|
||||
fun assumeReadableFile(path: Path, other: String): Path = assumeReadableFile(path / other)
|
||||
fun assumeReadableFile(path: Path, other: Path): Path = assumeReadableFile(path / other)
|
||||
|
||||
fun assumeDirectory(path: Path): Path {
|
||||
withClue("sanity check; should be directory: $path") {
|
||||
path.isDirectory() shouldBe true
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
fun assumeDirectory(pathStr: String): Path = assumeDirectory(Path(pathStr))
|
||||
fun assumeDirectory(path: Path, other: String): Path = assumeDirectory(path / other)
|
||||
fun assumeDirectory(pathStr: String, other: String): Path = assumeDirectory(Path(pathStr) / other)
|
||||
fun assumeDirectory(pathStr: String, other: Path): Path = assumeDirectory(Path(pathStr) / other)
|
||||
fun assumeDirectory(pathStr: String): Path = assumeDirectory(Path(pathStr))
|
||||
fun assumeDirectory(path: Path, other: String): Path = assumeDirectory(path / other)
|
||||
fun assumeDirectory(pathStr: String, other: String): Path = assumeDirectory(Path(pathStr) / other)
|
||||
fun assumeDirectory(pathStr: String, other: Path): Path = assumeDirectory(Path(pathStr) / other)
|
||||
|
||||
|
||||
@Deprecated(
|
||||
"Directories are checked automatically at init.",
|
||||
ReplaceWith("/* nothing */")
|
||||
)
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun sanityCheckDirectories(workingDirName: String? = null) {
|
||||
}
|
||||
|
||||
}
|
||||
@Deprecated(
|
||||
"Directories are checked automatically at init.",
|
||||
ReplaceWith("/* nothing */")
|
||||
)
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun sanityCheckDirectories(workingDirName: String? = null) {
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8tests.helpers.Helpers
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.div
|
||||
|
||||
@@ -20,21 +20,21 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithOnePathArg") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
withClue("should return the path") {
|
||||
Helpers.assumeNotExists(path) shouldBe path
|
||||
assumeNotExists(path) shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir / "ast_simple_main.p8")
|
||||
assumeNotExists(fixturesDir / "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir)
|
||||
assumeNotExists(fixturesDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,22 +42,22 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithOneStringArg") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
withClue("should return the path") {
|
||||
Helpers.assumeNotExists("$path") shouldBe path
|
||||
assumeNotExists("$path") shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
Helpers.assumeNotExists("$path")
|
||||
assumeNotExists("$path")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
Helpers.assumeNotExists("${Helpers.fixturesDir}")
|
||||
assumeNotExists("${fixturesDir}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,21 +65,21 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithPathAndStringArgs") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
withClue("should return the path") {
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir / "i_do_not_exist") shouldBe path
|
||||
assumeNotExists(fixturesDir / "i_do_not_exist") shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
assumeNotExists(fixturesDir, "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
Helpers.assumeNotExists(Helpers.fixturesDir, "..")
|
||||
assumeNotExists(fixturesDir, "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,46 +89,46 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
context("WithOnePathArg") {
|
||||
test("on non-existing path") {
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory(path)
|
||||
assumeDirectory(path)
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory(path)
|
||||
assumeDirectory(path)
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = Helpers.workingDir
|
||||
val path = workingDir
|
||||
withClue("should return the path") {
|
||||
Helpers.assumeDirectory(path) shouldBe path
|
||||
assumeDirectory(path) shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("WithOneStringArg") {
|
||||
test("on non-existing path") {
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory("$path")
|
||||
assumeDirectory("$path")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory("$path")
|
||||
assumeDirectory("$path")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = Helpers.workingDir
|
||||
val path = workingDir
|
||||
withClue("should return the path") {
|
||||
Helpers.assumeDirectory("$path") shouldBe path
|
||||
assumeDirectory("$path") shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,20 +136,20 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithPathAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory(Helpers.fixturesDir, "i_do_not_exist")
|
||||
assumeDirectory(fixturesDir, "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory(Helpers.fixturesDir, "ast_simple_main.p8")
|
||||
assumeDirectory(fixturesDir, "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = Helpers.workingDir / ".."
|
||||
val path = workingDir / ".."
|
||||
withClue("should return resulting path") {
|
||||
Helpers.assumeDirectory(Helpers.workingDir / "..") shouldBe path
|
||||
assumeDirectory(workingDir / "..") shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,20 +157,20 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithStringAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory("${Helpers.fixturesDir}", "i_do_not_exist")
|
||||
assumeDirectory("${fixturesDir}", "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory("${Helpers.fixturesDir}", "ast_simple_main.p8")
|
||||
assumeDirectory("${fixturesDir}", "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = Helpers.workingDir / ".."
|
||||
val path = workingDir / ".."
|
||||
withClue("should return resulting path") {
|
||||
Helpers.assumeDirectory(Helpers.workingDir / "..") shouldBe path
|
||||
assumeDirectory(workingDir / "..") shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,20 +178,20 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithStringAndPathArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory("${Helpers.fixturesDir}", Path("i_do_not_exist"))
|
||||
assumeDirectory("${fixturesDir}", Path("i_do_not_exist"))
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeDirectory("${Helpers.fixturesDir}", Path("ast_simple_main.p8"))
|
||||
assumeDirectory("${fixturesDir}", Path("ast_simple_main.p8"))
|
||||
}
|
||||
}
|
||||
|
||||
test("on existing directory") {
|
||||
val path = Helpers.workingDir / ".."
|
||||
val path = workingDir / ".."
|
||||
withClue("should return resulting path") {
|
||||
Helpers.assumeDirectory(Helpers.workingDir / Path("..")) shouldBe path
|
||||
assumeDirectory(workingDir / Path("..")) shouldBe path
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,22 +202,22 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithOnePathArg") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeReadableFile(path)
|
||||
assumeReadableFile(path)
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
withClue("should return the path") {
|
||||
Helpers.assumeReadableFile(path) shouldBe path
|
||||
assumeReadableFile(path) shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir)
|
||||
assumeReadableFile(fixturesDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,22 +225,22 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithOneStringArg") {
|
||||
|
||||
test("on non-existing path") {
|
||||
val path = Helpers.fixturesDir / "i_do_not_exist"
|
||||
val path = fixturesDir / "i_do_not_exist"
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeReadableFile("$path")
|
||||
assumeReadableFile("$path")
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
withClue("should return the resulting path") {
|
||||
Helpers.assumeReadableFile("$path") shouldBe path
|
||||
assumeReadableFile("$path") shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeReadableFile("${Helpers.fixturesDir}")
|
||||
assumeReadableFile("${fixturesDir}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,20 +248,20 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithPathAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, "i_do_not_exist")
|
||||
assumeReadableFile(fixturesDir, "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
val path = Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
withClue("should return the resulting path") {
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir / "ast_simple_main.p8") shouldBe path
|
||||
assumeReadableFile(fixturesDir / "ast_simple_main.p8") shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, "..")
|
||||
assumeReadableFile(fixturesDir, "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,19 +269,19 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithPathAndPathArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, Path("i_do_not_exist"))
|
||||
assumeReadableFile(fixturesDir, Path("i_do_not_exist"))
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
withClue("should return the resulting path") {
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir / Path("ast_simple_main.p8")) shouldBe Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
assumeReadableFile(fixturesDir / Path("ast_simple_main.p8")) shouldBe fixturesDir / "ast_simple_main.p8"
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir, Path(".."))
|
||||
assumeReadableFile(fixturesDir, Path(".."))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,19 +289,19 @@ class PathsHelpersTests: FunSpec({
|
||||
context("WithStringAndStringArgs") {
|
||||
test("on non-existing path") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
Helpers.assumeReadableFile("${Helpers.fixturesDir}", "i_do_not_exist")
|
||||
assumeReadableFile("${fixturesDir}", "i_do_not_exist")
|
||||
}
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
withClue("should return the resulting path") {
|
||||
Helpers.assumeReadableFile(Helpers.fixturesDir / "ast_simple_main.p8") shouldBe Helpers.fixturesDir / "ast_simple_main.p8"
|
||||
assumeReadableFile(fixturesDir / "ast_simple_main.p8") shouldBe fixturesDir / "ast_simple_main.p8"
|
||||
}
|
||||
}
|
||||
|
||||
test("on directory") {
|
||||
shouldThrow<AssertionError> {
|
||||
Helpers.assumeReadableFile("${Helpers.fixturesDir}", "..")
|
||||
assumeReadableFile("${fixturesDir}", "..")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package prog8tests.vm
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.codegen.virtual.*
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.VmDataType
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
||||
class TestVmPeepholeOpt: FunSpec({
|
||||
fun makeVmProgram(lines: List<VmCodeLine>): Pair<AssemblyProgram, VariableAllocator> {
|
||||
val st = SymbolTable()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val allocations = VariableAllocator(st, program)
|
||||
val asm = AssemblyProgram("test", allocations)
|
||||
val block = VmCodeChunk()
|
||||
for(line in lines)
|
||||
block += line
|
||||
asm.addBlock(block)
|
||||
return Pair(asm, allocations)
|
||||
}
|
||||
|
||||
fun AssemblyProgram.lines(): List<VmCodeLine> = this.getBlocks().flatMap { it.lines }
|
||||
|
||||
test("remove nops") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("dummy")),
|
||||
VmCodeInstruction(Opcode.NOP),
|
||||
VmCodeInstruction(Opcode.NOP)
|
||||
))
|
||||
asm.lines().size shouldBe 3
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
opt.optimize()
|
||||
asm.lines().size shouldBe 1
|
||||
}
|
||||
|
||||
test("remove jmp to label below") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("label")), // removed
|
||||
VmCodeLabel(listOf("label")),
|
||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("label2")), // removed
|
||||
VmCodeInstruction(Opcode.NOP), // removed
|
||||
VmCodeLabel(listOf("label2")),
|
||||
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf("label3")),
|
||||
VmCodeInstruction(Opcode.INC, VmDataType.BYTE, reg1=1),
|
||||
VmCodeLabel(listOf("label3"))
|
||||
))
|
||||
asm.lines().size shouldBe 8
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 5
|
||||
(lines[0] as VmCodeLabel).name shouldBe listOf("label")
|
||||
(lines[1] as VmCodeLabel).name shouldBe listOf("label2")
|
||||
(lines[2] as VmCodeInstruction).ins.opcode shouldBe Opcode.JUMP
|
||||
(lines[3] as VmCodeInstruction).ins.opcode shouldBe Opcode.INC
|
||||
(lines[4] as VmCodeLabel).name shouldBe listOf("label3")
|
||||
}
|
||||
|
||||
test("remove double sec/clc") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.SEC),
|
||||
VmCodeInstruction(Opcode.SEC),
|
||||
VmCodeInstruction(Opcode.SEC),
|
||||
VmCodeInstruction(Opcode.CLC),
|
||||
VmCodeInstruction(Opcode.CLC),
|
||||
VmCodeInstruction(Opcode.CLC)
|
||||
))
|
||||
asm.lines().size shouldBe 6
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 1
|
||||
(lines[0] as VmCodeInstruction).ins.opcode shouldBe Opcode.CLC
|
||||
}
|
||||
|
||||
test("push followed by pop") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=42),
|
||||
VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=42),
|
||||
VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=99),
|
||||
VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=222)
|
||||
))
|
||||
asm.lines().size shouldBe 4
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 1
|
||||
(lines[0] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOADR
|
||||
(lines[0] as VmCodeInstruction).ins.reg1 shouldBe 222
|
||||
(lines[0] as VmCodeInstruction).ins.reg2 shouldBe 99
|
||||
}
|
||||
|
||||
test("remove useless div/mul, add/sub") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 1),
|
||||
VmCodeInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 1),
|
||||
VmCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 1),
|
||||
VmCodeInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 1),
|
||||
VmCodeInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 2),
|
||||
VmCodeInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 2),
|
||||
VmCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 2),
|
||||
VmCodeInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 2),
|
||||
VmCodeInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 0),
|
||||
VmCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 0)
|
||||
))
|
||||
asm.lines().size shouldBe 10
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 4
|
||||
}
|
||||
|
||||
test("replace add/sub 1 by inc/dec") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 1),
|
||||
VmCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
asm.lines().size shouldBe 2
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 2
|
||||
(lines[0] as VmCodeInstruction).ins.opcode shouldBe Opcode.INC
|
||||
(lines[1] as VmCodeInstruction).ins.opcode shouldBe Opcode.DEC
|
||||
}
|
||||
|
||||
test("remove useless and/or/xor") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 255),
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 65535),
|
||||
VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 0),
|
||||
VmCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 0),
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 200),
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 60000),
|
||||
VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 1),
|
||||
VmCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
asm.lines().size shouldBe 8
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 4
|
||||
}
|
||||
|
||||
test("replace and/or/xor by constant number") {
|
||||
val(asm, allocations) = makeVmProgram(listOf(
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 0),
|
||||
VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 0),
|
||||
VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 255),
|
||||
VmCodeInstruction(Opcode.OR, VmDataType.WORD, reg1=42, value = 65535)
|
||||
))
|
||||
asm.lines().size shouldBe 4
|
||||
val opt = VmPeepholeOptimizer(asm, allocations)
|
||||
opt.optimize()
|
||||
val lines = asm.lines()
|
||||
lines.size shouldBe 4
|
||||
(lines[0] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
||||
(lines[1] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
||||
(lines[2] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
||||
(lines[3] as VmCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
||||
(lines[0] as VmCodeInstruction).ins.value shouldBe 0
|
||||
(lines[1] as VmCodeInstruction).ins.value shouldBe 0
|
||||
(lines[2] as VmCodeInstruction).ins.value shouldBe 255
|
||||
(lines[3] as VmCodeInstruction).ins.value shouldBe 65535
|
||||
}
|
||||
})
|
||||
@@ -93,7 +93,8 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
DataType.ARRAY_UW -> "uword["
|
||||
DataType.ARRAY_W -> "word["
|
||||
DataType.ARRAY_F -> "float["
|
||||
else -> "?????"
|
||||
DataType.ARRAY_BOOL -> "bool["
|
||||
else -> throw IllegalArgumentException("weird dt: $dt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +326,6 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
override fun visit(assignment: Assignment) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null && binExpr.left isSameAs assignment.target
|
||||
&& binExpr.operator !in arrayOf("and", "or", "xor")
|
||||
&& binExpr.operator !in ComparisonOperators) {
|
||||
// we only support the inplace assignments of the form A = A <operator> <value>
|
||||
assignment.target.accept(this)
|
||||
|
||||
@@ -77,7 +77,7 @@ interface IStatementContainer {
|
||||
|
||||
fun searchSymbol(name: String): Statement? {
|
||||
if(this is Subroutine && isAsmSubroutine)
|
||||
return searchAsmParameter(name)
|
||||
return searchParameter(name)
|
||||
|
||||
// this is called quite a lot and could perhaps be optimized a bit more,
|
||||
// but adding a memoization cache didn't make much of a practical runtime difference...
|
||||
@@ -138,6 +138,10 @@ interface IStatementContainer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this is Subroutine && !isAsmSubroutine)
|
||||
return searchParameter(name)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -339,6 +343,7 @@ internal object BuiltinFunctionScopePlaceholder : INameScope {
|
||||
|
||||
|
||||
fun defaultZero(dt: DataType, position: Position) = when(dt) {
|
||||
DataType.BOOL -> NumericLiteral(DataType.UBYTE, 0.0, position)
|
||||
DataType.UBYTE -> NumericLiteral(DataType.UBYTE, 0.0, position)
|
||||
DataType.BYTE -> NumericLiteral(DataType.BYTE, 0.0, position)
|
||||
DataType.UWORD, DataType.STR -> NumericLiteral(DataType.UWORD, 0.0, position)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
@@ -11,7 +14,7 @@ import prog8.code.core.ZeropageWish
|
||||
fun Program.getTempVar(dt: DataType, altNames: Boolean=false): Pair<List<String>, VarDecl> {
|
||||
val tmpvarName = if(altNames) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> listOf("prog8_lib", "tempvar_ub2")
|
||||
DataType.UBYTE, DataType.BOOL -> listOf("prog8_lib", "tempvar_ub2")
|
||||
DataType.BYTE -> listOf("prog8_lib", "tempvar_b2")
|
||||
DataType.UWORD -> listOf("prog8_lib", "tempvar_uw2")
|
||||
DataType.WORD -> listOf("prog8_lib", "tempvar_w2")
|
||||
@@ -20,7 +23,7 @@ fun Program.getTempVar(dt: DataType, altNames: Boolean=false): Pair<List<String>
|
||||
}
|
||||
} else {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> listOf("prog8_lib", "tempvar_ub")
|
||||
DataType.UBYTE, DataType.BOOL -> listOf("prog8_lib", "tempvar_ub")
|
||||
DataType.BYTE -> listOf("prog8_lib", "tempvar_b")
|
||||
DataType.UWORD -> listOf("prog8_lib", "tempvar_uw")
|
||||
DataType.WORD -> listOf("prog8_lib", "tempvar_w")
|
||||
@@ -48,6 +51,7 @@ fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
|
||||
return when {
|
||||
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
|
||||
dt istype DataType.UBYTE -> listOf("cx16", "r9L")
|
||||
dt istype DataType.BOOL -> listOf("cx16", "r9L")
|
||||
dt istype DataType.BYTE -> listOf("cx16", "r9sL")
|
||||
dt istype DataType.UWORD -> listOf("cx16", "r9")
|
||||
dt istype DataType.WORD -> listOf("cx16", "r9s")
|
||||
|
||||
@@ -143,4 +143,23 @@ class Program(val name: String,
|
||||
}
|
||||
}
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
|
||||
fun makeLabel(postfix: String): String {
|
||||
generatedLabelSequenceNumber++
|
||||
return "${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
|
||||
}
|
||||
|
||||
fun makeLabel(postfix: String, position: Position): Label {
|
||||
val strLabel = makeLabel(postfix)
|
||||
return Label(strLabel, position)
|
||||
}
|
||||
|
||||
fun jumpLabel(label: Label): Jump {
|
||||
val ident = IdentifierReference(listOf(label.name), label.position)
|
||||
return Jump(null, ident, null, label.position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const val generatedLabelPrefix = "prog8_label_"
|
||||
@@ -10,6 +10,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.floor
|
||||
@@ -109,7 +110,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
DataType.UWORD -> NumericLiteral(DataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
|
||||
}
|
||||
"not" -> NumericLiteral.fromBoolean(constval.number == 0.0, constval.position)
|
||||
"not" -> NumericLiteral.fromBoolean(constval.number==0.0, constval.position)
|
||||
else -> throw FatalAstException("invalid operator")
|
||||
}
|
||||
converted.linkParents(this.parent)
|
||||
@@ -123,7 +124,8 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
val inferred = expression.inferType(program)
|
||||
return when(operator) {
|
||||
"+" -> inferred
|
||||
"~", "not" -> {
|
||||
"not" -> InferredTypes.knownFor(DataType.BOOL)
|
||||
"~" -> {
|
||||
when(inferred.getOr(DataType.UNDEFINED)) {
|
||||
in ByteDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
|
||||
in WordDatatypes -> InferredTypes.knownFor(DataType.UWORD)
|
||||
@@ -184,15 +186,15 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
val leftDt = left.inferType(program)
|
||||
val rightDt = right.inferType(program)
|
||||
|
||||
fun dynamicBooleanType(): InferredTypes.InferredType {
|
||||
// as a special case, an expression yielding a boolean result, adapts the result
|
||||
// type to what is required (byte or word), to avoid useless type casting
|
||||
return when (parent) {
|
||||
is TypecastExpression -> InferredTypes.InferredType.known((parent as TypecastExpression).type)
|
||||
is Assignment -> (parent as Assignment).target.inferType(program)
|
||||
else -> InferredTypes.InferredType.known(DataType.UBYTE)
|
||||
}
|
||||
}
|
||||
// fun dynamicBooleanType(): InferredTypes.InferredType {
|
||||
// // as a special case, an expression yielding a boolean result, adapts the result
|
||||
// // type to what is required (byte or word), to avoid useless type casting
|
||||
// return when (parent) {
|
||||
// is TypecastExpression -> InferredTypes.InferredType.known((parent as TypecastExpression).type)
|
||||
// is Assignment -> (parent as Assignment).target.inferType(program)
|
||||
// else -> InferredTypes.InferredType.known(DataType.BOOL) // or UBYTE?
|
||||
// }
|
||||
// }
|
||||
|
||||
return when (operator) {
|
||||
"+", "-", "*", "%", "/" -> {
|
||||
@@ -212,15 +214,12 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
}
|
||||
}
|
||||
}
|
||||
"&" -> leftDt
|
||||
"|" -> leftDt
|
||||
"^" -> leftDt
|
||||
"and", "or", "xor" -> InferredTypes.knownFor(DataType.UBYTE)
|
||||
"&", "|", "^" -> if(leftDt istype DataType.BOOL) InferredTypes.knownFor(DataType.UBYTE) else leftDt
|
||||
"and", "or", "xor", "not" -> InferredTypes.knownFor(DataType.BOOL)
|
||||
"<", ">",
|
||||
"<=", ">=",
|
||||
"==", "!=" -> dynamicBooleanType()
|
||||
"==", "!=", "in" -> InferredTypes.knownFor(DataType.BOOL)
|
||||
"<<", ">>" -> leftDt
|
||||
"in" -> dynamicBooleanType()
|
||||
else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
|
||||
}
|
||||
}
|
||||
@@ -442,7 +441,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
|
||||
companion object {
|
||||
fun fromBoolean(bool: Boolean, position: Position) =
|
||||
NumericLiteral(DataType.UBYTE, if (bool) 1.0 else 0.0, position)
|
||||
NumericLiteral(DataType.UBYTE, if(bool) 1.0 else 0.0, position)
|
||||
|
||||
fun optimalNumeric(value: Number, position: Position): NumericLiteral {
|
||||
val digits = floor(value.toDouble()) - value.toDouble()
|
||||
@@ -480,6 +479,11 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
if(type==DataType.BOOL)
|
||||
throw FatalAstException("should not create NumericLiteral with BOOL type @$position")
|
||||
}
|
||||
|
||||
val asBooleanValue: Boolean = number != 0.0
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -539,6 +543,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(targettype== DataType.UBYTE) {
|
||||
@@ -557,6 +563,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(targettype== DataType.BYTE && number <= 127)
|
||||
@@ -567,6 +575,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(targettype== DataType.BYTE && number >= -128 && number <=127)
|
||||
@@ -585,6 +595,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
}
|
||||
if(targettype== DataType.FLOAT)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
try {
|
||||
@@ -596,11 +608,18 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if (targettype == DataType.UWORD && number >= 0 && number <= 65535)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
} catch (x: ExpressionError) {
|
||||
return CastValue(false, null)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
DataType.BOOL -> {
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("type cast of weird type $type")
|
||||
}
|
||||
}
|
||||
return CastValue(false, null)
|
||||
}
|
||||
@@ -1090,7 +1109,7 @@ class ContainmentCheck(var element: Expression,
|
||||
return iterable.referencesIdentifier(nameInSource)
|
||||
}
|
||||
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UBYTE)
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.BOOL)
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if(replacement !is Expression)
|
||||
@@ -1142,7 +1161,13 @@ class BuiltinFunctionCall(override var target: IdentifierReference,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteral? = null
|
||||
override fun constValue(program: Program): NumericLiteral? {
|
||||
val function = BuiltinFunctions.getValue(name)
|
||||
if(function.pure) {
|
||||
return program.builtinFunctions.constValue(name, args, position)
|
||||
}
|
||||
return null
|
||||
}
|
||||
override fun toString() = "BuiltinFunctionCall(name=$name, pos=$position)"
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@@ -47,6 +47,7 @@ object InferredTypes {
|
||||
infix fun isNotAssignableTo(targetDt: InferredType): Boolean = !this.isAssignableTo(targetDt)
|
||||
infix fun isNotAssignableTo(targetDt: DataType): Boolean = !this.isAssignableTo(targetDt)
|
||||
|
||||
val isBool get() = datatype == DataType.BOOL
|
||||
val isBytes get() = datatype in ByteDatatypes
|
||||
val isWords get() = datatype in WordDatatypes
|
||||
val isInteger get() = datatype in IntegerDatatypes
|
||||
@@ -67,12 +68,14 @@ object InferredTypes {
|
||||
DataType.UWORD to InferredType.known(DataType.UWORD),
|
||||
DataType.WORD to InferredType.known(DataType.WORD),
|
||||
DataType.FLOAT to InferredType.known(DataType.FLOAT),
|
||||
DataType.BOOL to InferredType.known(DataType.BOOL),
|
||||
DataType.STR to InferredType.known(DataType.STR),
|
||||
DataType.ARRAY_UB to InferredType.known(DataType.ARRAY_UB),
|
||||
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
|
||||
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),
|
||||
DataType.ARRAY_W to InferredType.known(DataType.ARRAY_W),
|
||||
DataType.ARRAY_F to InferredType.known(DataType.ARRAY_F)
|
||||
DataType.ARRAY_F to InferredType.known(DataType.ARRAY_F),
|
||||
DataType.ARRAY_BOOL to InferredType.known(DataType.ARRAY_BOOL)
|
||||
)
|
||||
|
||||
fun void() = voidInstance
|
||||
|
||||
@@ -228,6 +228,7 @@ class VarDecl(val type: VarDeclType,
|
||||
DataType.UWORD -> DataType.ARRAY_UW
|
||||
DataType.WORD -> DataType.ARRAY_W
|
||||
DataType.FLOAT -> DataType.ARRAY_F
|
||||
DataType.BOOL -> DataType.ARRAY_BOOL
|
||||
DataType.STR -> DataType.ARRAY_UW // use memory address of the string instead
|
||||
else -> {
|
||||
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats/strings(ptrs)", position))
|
||||
@@ -770,9 +771,7 @@ class Subroutine(override val name: String,
|
||||
// code to provide the ability to reference asmsub parameters via qualified name:
|
||||
private val asmParamsDecls = mutableMapOf<String, VarDecl>()
|
||||
|
||||
fun searchAsmParameter(name: String): VarDecl? {
|
||||
require(isAsmSubroutine)
|
||||
|
||||
fun searchParameter(name: String): VarDecl? {
|
||||
val existingDecl = asmParamsDecls[name]
|
||||
if(existingDecl!=null)
|
||||
return existingDecl
|
||||
|
||||
@@ -65,6 +65,18 @@ interface IAstModification {
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceNodeSafe(val node: Node, private val replacement: Node, private val parent: Node) :
|
||||
IAstModification {
|
||||
override fun perform() {
|
||||
try {
|
||||
parent.replaceChildNode(node, replacement)
|
||||
replacement.linkParents(parent)
|
||||
} catch (fa: FatalAstException) {
|
||||
// possibly because of another replacement. Ignore here, we try again later.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SwapOperands(private val expr: BinaryExpression): IAstModification {
|
||||
override fun perform() {
|
||||
require(expr.operator in AssociativeOperators)
|
||||
|
||||
@@ -94,19 +94,18 @@ private val functionSignatures: List<FSignature> = listOf(
|
||||
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
// cmp returns a status in the carry flag, but not a proper return value
|
||||
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
||||
FSignature("abs" , true, listOf(FParam("value", IntegerDatatypes)), DataType.UWORD, ::builtinAbs),
|
||||
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
|
||||
FSignature("abs" , true, listOf(FParam("value", IntegerDatatypesNoBool)), DataType.UWORD, ::builtinAbs),
|
||||
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD, ::builtinLen),
|
||||
// normal functions follow:
|
||||
FSignature("sizeof" , true, listOf(FParam("object", DataType.values())), DataType.UBYTE, ::builtinSizeof),
|
||||
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE, ::builtinSgn ),
|
||||
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()) } },
|
||||
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x and 255).toDouble() } },
|
||||
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x ushr 8 and 255).toDouble()} },
|
||||
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
FSignature("boolean" , true, listOf(FParam("value", NumericDatatypes)), DataType.UBYTE, ::builtinBoolean),
|
||||
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
@@ -179,7 +178,7 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
|
||||
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
return when (constval.type) {
|
||||
in IntegerDatatypes -> NumericLiteral.optimalInteger(abs(constval.number.toInt()), args[0].position)
|
||||
in IntegerDatatypesNoBool -> NumericLiteral.optimalInteger(abs(constval.number.toInt()), args[0].position)
|
||||
else -> throw SyntaxError("abs requires one integer argument", position)
|
||||
}
|
||||
}
|
||||
@@ -227,7 +226,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||
|
||||
return when(target.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F -> {
|
||||
in ArrayDatatypes -> {
|
||||
arraySize = target.arraysize?.constIndex()
|
||||
if(arraySize==null)
|
||||
throw CannotEvaluateException("len", "arraysize unknown")
|
||||
@@ -251,23 +250,6 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
|
||||
return NumericLiteral(DataType.UWORD, result.toDouble(), position)
|
||||
}
|
||||
|
||||
private fun builtinBoolean(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
if (args.size != 1)
|
||||
throw SyntaxError("boolean requires one argument", position)
|
||||
val constvalue = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
return if(constvalue.number==0.0)
|
||||
NumericLiteral(DataType.UBYTE, 0.0, args[0].position)
|
||||
else if(constvalue.type==DataType.UBYTE)
|
||||
constvalue
|
||||
else if(constvalue.type==DataType.FLOAT)
|
||||
NumericLiteral(DataType.UBYTE, constvalue.number.sign, args[0].position)
|
||||
else {
|
||||
val value = constvalue.number.toInt()
|
||||
val byteValue = ((value ushr 8) or value) and 255
|
||||
NumericLiteral(DataType.UBYTE, byteValue.toDouble(), args[0].position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
if (args.size != 1)
|
||||
throw SyntaxError("sgn requires one argument", position)
|
||||
|
||||
@@ -157,6 +157,19 @@ One or more .p8 module files
|
||||
``-expericodegen``
|
||||
Use experimental code generation backend (*incomplete*).
|
||||
|
||||
``-vm``
|
||||
load and run a p8-virt listing in the internal VirtualMachine instead of compiling a prog8 program file..
|
||||
|
||||
``-D SYMBOLNAME=VALUE``
|
||||
Add this user-defined symbol directly to the beginning of the generated assembly file.
|
||||
Can be repeated to define multiple symbols.
|
||||
|
||||
``-esa <address>``
|
||||
Override the base address of the evaluation stack. Has to be page-aligned.
|
||||
You can specify an integer or hexadecimal address.
|
||||
When not compiling for the CommanderX16 target, the location of the 16 virtual registers cx16.r0..r15
|
||||
is changed accordingly (to keep them in the same memory space as the evaluation stack).
|
||||
|
||||
|
||||
Module source code files
|
||||
------------------------
|
||||
|
||||
@@ -364,3 +364,9 @@ Also contains a helper function to calculate the file size of a loaded file (alt
|
||||
to 16 bits, 64Kb)
|
||||
|
||||
|
||||
psg (cx16 only)
|
||||
----------------
|
||||
Available for the Cx16 target.
|
||||
Contains a simple abstraction for the Vera's PSG (programmable sound generator) to play simple waveforms.
|
||||
It includes an interrupt routine to handle simple Attack/Release envelopes as well.
|
||||
See the examples/cx16/bdmusic.p8 program for ideas how to use it.
|
||||
@@ -237,6 +237,20 @@ The signed integers integers are in the range -128..127 for bytes,
|
||||
and -32768..32767 for words.
|
||||
|
||||
|
||||
Boolean values
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
These values are only ``true`` or ``false``, or 1 or 0. An integer's "truthy" value (i.e. a number
|
||||
converted to boolean) is ``false`` (0) when it is zero and ``true`` (1) for all other values.
|
||||
Logical expressions, comparisons and some other code might compile more efficiently if
|
||||
you explicitly use ``bool`` types instead of integers there because that will avoid this conversion.
|
||||
In the end the compiler translates boolean variables to a byte that stores just 0 or 1.
|
||||
|
||||
If you find that you need a whole bunch of boolean variables or perhaps even an array of them,
|
||||
consider using bit masks in regular integer variables instead.
|
||||
This saves a lot of memory and may be faster as well.
|
||||
|
||||
|
||||
Floating point numbers
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -645,7 +659,9 @@ Arithmetic and Logical expressions
|
||||
Arithmetic expressions are expressions that calculate a numeric result (integer or floating point).
|
||||
Many common arithmetic operators can be used and follow the regular precedence rules.
|
||||
Logical expressions are expressions that calculate a boolean result: true or false
|
||||
(which in reality are just a 1 or 0 integer value).
|
||||
(which in reality are just a 1 or 0 integer value). When using variables of the type ``bool``,
|
||||
logical expressions will compile more efficiently than when you're using regular integer type operands
|
||||
(because these have to be converted to 0 or 1 every time)
|
||||
|
||||
You can use parentheses to group parts of an expresion to change the precedence.
|
||||
Usually the normal precedence rules apply (``*`` goes before ``+`` etc.) but subexpressions
|
||||
@@ -775,10 +791,6 @@ sort(array)
|
||||
Miscellaneous
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
boolean(x)
|
||||
Returns a byte value representing the boolean (truthy) value of x, where x can be any numeric type.
|
||||
This means it returns 0 (false) when x equals 0, and a value other than 0 otherwise.
|
||||
|
||||
cmp(x,y)
|
||||
Compare the integer value x to integer value y. Doesn't return a value or boolean result, only sets the processor's status bits!
|
||||
You can use a conditional jumps (``if_cc`` etcetera) to act on this.
|
||||
|
||||
@@ -300,7 +300,7 @@ type identifier type storage size example var declara
|
||||
=============== ======================= ================= =========================================
|
||||
``byte`` signed byte 1 byte = 8 bits ``byte myvar = -22``
|
||||
``ubyte`` unsigned byte 1 byte = 8 bits ``ubyte myvar = $8f``, ``ubyte c = 'a'``
|
||||
-- boolean 1 byte = 8 bits ``byte myvar = true`` or ``byte myvar == false``
|
||||
``bool`` boolean 1 byte = 8 bits ``bool myvar = true`` or ``bool myvar == false``
|
||||
The true and false are actually just aliases
|
||||
for the byte values 1 and 0.
|
||||
``word`` signed word 2 bytes = 16 bits ``word myvar = -12345``
|
||||
@@ -312,11 +312,13 @@ type identifier type storage size example var declara
|
||||
``word[x]`` signed word array 2*x bytes ``word[4] myvar``
|
||||
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar``
|
||||
``float[x]`` floating-point array 5*x bytes ``float[4] myvar``
|
||||
``bool[x]`` boolean array 5*x bytes ``bool[4] myvar`` note: consider using bit flags in a byte or word instead to save space
|
||||
``byte[]`` signed byte array depends on value ``byte[] myvar = [1, 2, 3, 4]``
|
||||
``ubyte[]`` unsigned byte array depends on value ``ubyte[] myvar = [1, 2, 3, 4]``
|
||||
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
|
||||
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
|
||||
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
|
||||
``bool[]`` boolean array depends on value ``bool[] myvar = [true, false, true]`` note: consider using bit flags in a byte or word instead to save space
|
||||
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
|
||||
``str`` string (petscii) varies ``str myvar = "hello."``
|
||||
implicitly terminated by a 0-byte
|
||||
@@ -486,12 +488,17 @@ logical: ``not`` ``and`` ``or`` ``xor``
|
||||
These operators are the usual logical operations that are part of a logical expression to reason
|
||||
about truths (boolean values). The result of such an expression is a 'boolean' value 'true' or 'false'
|
||||
(which in reality is just a byte value of 1 or 0).
|
||||
Notice that the expression ``not x`` is equivalent to ``x==0``, and the compiler will treat it as such.
|
||||
|
||||
.. note::
|
||||
You can use regular integers directly in logical expressions but these have to be converted to
|
||||
the boolean value 0 or 1 every time, resulting in larger and slower code. Consider using
|
||||
the ``bool`` variable type instead, where this conversion doesn't need to occur.
|
||||
|
||||
.. note::
|
||||
Unlike most other programming languages, there is no short-cirquit or McCarthy-evaluation
|
||||
for the ``and`` and ``or`` operators at this time. This means that prog8 currently always evaluates
|
||||
all operands from these logical expressions, even when one of them already determines the outcome.
|
||||
This may be changed in a future language version.
|
||||
for the logical ``and`` and ``or`` operators. This means that prog8 currently always evaluates
|
||||
all operands from these logical expressions, even when one of them already determines the outcome!
|
||||
|
||||
range creation: ``to``
|
||||
Creates a range of values from the LHS value to the RHS value, inclusive.
|
||||
|
||||
@@ -156,3 +156,10 @@ And for the Commander X16::
|
||||
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables kernal irq handler! sys.wait() won't work anymore
|
||||
cx16.restore_irq() ; set everything back to the systems default irq handler
|
||||
|
||||
|
||||
The Commander X16 provides two additional routines that should be used *in your IRQ handler routine* if it uses the Vera registers::
|
||||
|
||||
cx16.push_vera_context()
|
||||
; ... do your work that uses vera here...
|
||||
cx15.pop_vera_context()
|
||||
|
||||
+10
-2
@@ -17,15 +17,23 @@ Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Compiler:
|
||||
|
||||
- add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value?
|
||||
- on non-cx16 targets: have an option that if zeropage=FULL, moves the cx16 virtual registers to ZP (same location as on x16?)
|
||||
needs the dynamic base address for the symbols in syslib.p8
|
||||
also needs a trick to allocate them in ZP like Cx16Zeropage already does
|
||||
- vm Instruction needs to know what the read-registers/memory are, and what the write-register/memory is.
|
||||
this info is needed for more advanced optimizations and later code generation steps.
|
||||
- vm: implement remaining sin/cos functions in math.p8
|
||||
- vm: find a solution for the cx16.r0..r15 that "overlap" (r0, r0L, r0H etc) but in the vm each get their own separate variable location now
|
||||
- vm: somehow deal with asmsubs otherwise the vm IR can't fully encode all of prog8
|
||||
- vm: don't store symbol names in instructions to make optimizing the IR easier? but what about jumps to labels. And it's no longer readable by humans.
|
||||
- vm: how to remove all unused subroutines? (in the 6502 assembly codegen, we let 64tass solve this for us)
|
||||
- vm: rather than being able to jump to any 'address' (IPTR), use 'blocks' that have entry and exit points -> even better dead code elimination possible too
|
||||
- vm: add ore optimizations in VmPeepholeOptimizer
|
||||
- move the vm unit tests to codeGenVirtual module and remove virtualmachine dependency in the compiler module
|
||||
- when the vm is stable and *if* its language can get promoted to prog8 IL, the variable allocation should be changed.
|
||||
It's now done before the vm code generation, but the IL should probably not depend on the allocations already performed.
|
||||
So the CodeGen doesn't do VariableAlloc *before* the codegen, but as a last step.
|
||||
- generate WASM from the new ast (or from vm code?) to run prog8 on a browser canvas?
|
||||
- createAssemblyAndAssemble(): make it possible to actually get rid of the VarDecl nodes by fixing the rest of the code mentioned there.
|
||||
but probably better to rewrite the 6502 codegen on top of the new Ast.
|
||||
- simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation, but how does it know?
|
||||
@@ -69,7 +77,7 @@ Expressions:
|
||||
|
||||
Optimizations:
|
||||
|
||||
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once (if?) 6502-codegen is no longer done from
|
||||
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. When 6502-codegen is no longer done from
|
||||
the old CompilerAst, those checks should probably be removed, or be made permanent
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served
|
||||
- AssignmentAsmGen.assignExpression() -> improve code gen for assigning boolean comparison expressions
|
||||
|
||||
Loaded 100 of 134 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user