mirror of
https://github.com/irmen/prog8.git
synced 2026-10-23 12:02:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e98e951834 | ||
|
|
2668bf8519 | ||
|
|
dd4c073e18 | ||
|
|
c7c72f00c7 | ||
|
|
ef1c665b9a | ||
|
|
d56565be25 | ||
|
|
e076b3aedc | ||
|
|
ae3b2ddf5f | ||
|
|
1bdc427d73 | ||
|
|
6a639ce533 | ||
|
|
d91ca8b197 | ||
|
|
a01c0a283d | ||
|
|
5c393091a0 | ||
|
|
01b680504b | ||
|
|
8e4319cd5a | ||
|
|
5a776dd690 | ||
|
|
cce08d95db | ||
|
|
28c1b208c1 | ||
|
|
3844bf1f72 | ||
|
|
745d192563 | ||
|
|
ee782e92ac | ||
|
|
afbc91d1fc | ||
|
|
f998888d6d | ||
|
|
7d8b42d63e | ||
|
|
6ebd4e821f | ||
|
|
d1806bfdc3 | ||
|
|
1d2d7155da | ||
|
|
b09e0a05bf | ||
|
|
c609e982fe | ||
|
|
2b227b43fe | ||
|
|
48f09f71ab | ||
|
|
ead8c59bda | ||
|
|
db52a9466c | ||
|
|
1509de390e | ||
|
|
88a1aa4f3d | ||
|
|
172e78e8f2 | ||
|
|
36bfef567d | ||
|
|
e40ebd75a2 | ||
|
|
992732f2cb | ||
|
|
b58a3ba1bb | ||
|
|
afe521b0c9 | ||
|
|
5d9caef45f | ||
|
|
278e2f5605 | ||
|
|
1e299bf360 | ||
|
|
8dfa0bc38c | ||
|
|
fde136fb7b | ||
|
|
ee4da1a757 | ||
|
|
ae2d96c455 | ||
|
|
6d8fbe0877 | ||
|
|
2fa1d8f2e8 | ||
|
|
533090a68e | ||
|
|
1dff59e1d6 | ||
|
|
44d232f52a | ||
|
|
5f6cff739a | ||
|
|
2764d235a9 | ||
|
|
45debff89f | ||
|
|
c45fbe6310 | ||
|
|
9ef9c24388 | ||
|
|
6a40f23578 | ||
|
|
6a0a6b4751 | ||
|
|
0bee6f6b41 | ||
|
|
82a15b5a16 | ||
|
|
11b7c4459e | ||
|
|
98570ac456 | ||
|
|
1b2296ad5b | ||
|
|
16851746d6 | ||
|
|
935450a45f | ||
|
|
ba67fd318b | ||
|
|
08ac459a41 | ||
|
|
a83e9d9a0a | ||
|
|
62d3f01948 | ||
|
|
af5ca2d0b8 | ||
|
|
ab4bcdf12d | ||
|
|
a6756d2cea | ||
|
|
f81061dd42 | ||
|
|
8e2c304b3c | ||
|
|
f21adaa3ef | ||
|
|
2637939e62 | ||
|
|
faf05582f8 | ||
|
|
161c02ced3 | ||
|
|
ff8de8e42d | ||
|
|
09d506194f | ||
|
|
42db3085df | ||
|
|
ad14c88fde | ||
|
|
0c9daf6eaf | ||
|
|
86c6530e46 | ||
|
|
159f80d629 |
No files matched your search
Generated
+24
-17
@@ -1,19 +1,26 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime">
|
||||
<CLASSES>
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
+5
-5
@@ -1,19 +1,19 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.7.2" />
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.8.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||
</CLASSES>
|
||||
|
||||
+11
-11
@@ -1,14 +1,14 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.7.2" />
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.8.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.7.2/kotest-runner-junit5-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.7.2/kotest-framework-api-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.8.0/kotest-runner-junit5-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.8.0/kotest-framework-api-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.7.0/kotlinx-coroutines-test-jvm-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.7.2/kotest-framework-engine-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.8.0/kotest-framework-engine-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/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!/" />
|
||||
@@ -18,12 +18,12 @@
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.7.2/kotest-framework-discovery-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.8.0/kotest-framework-discovery-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.7.2/kotest-extensions-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.7.2/kotest-framework-concurrency-jvm-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.8.0/kotest-extensions-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.8.0/kotest-framework-concurrency-jvm-5.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
|
||||
|
||||
@@ -120,8 +120,10 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"max__uword" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UWORD)), FParam("val2", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"peekf" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.FLOAT),
|
||||
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||
"pokef" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.FLOAT))), null),
|
||||
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
|
||||
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package prog8.code.core
|
||||
|
||||
enum class DataType {
|
||||
UBYTE, // pass by value
|
||||
BYTE, // pass by value
|
||||
UWORD, // pass by value
|
||||
WORD, // pass by value
|
||||
FLOAT, // pass by value
|
||||
BOOL, // pass by value
|
||||
UBYTE, // pass by value 8 bits unsigned
|
||||
BYTE, // pass by value 8 bits signed
|
||||
UWORD, // pass by value 16 bits unsigned
|
||||
WORD, // pass by value 16 bits signed
|
||||
LONG, // pass by value 32 bits signed
|
||||
FLOAT, // pass by value machine dependent
|
||||
BOOL, // pass by value bit 0 of a 8 bit byte
|
||||
STR, // pass by reference
|
||||
ARRAY_UB, // pass by reference
|
||||
ARRAY_B, // pass by reference
|
||||
@@ -23,11 +24,12 @@ enum class DataType {
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(this) {
|
||||
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)
|
||||
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, LONG, FLOAT)
|
||||
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT, BOOL)
|
||||
BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT)
|
||||
UWORD -> targetType.oneOf(UWORD, LONG, FLOAT)
|
||||
WORD -> targetType.oneOf(WORD, LONG, FLOAT)
|
||||
LONG -> targetType.oneOf(LONG, FLOAT)
|
||||
FLOAT -> targetType.oneOf(FLOAT)
|
||||
STR -> targetType.oneOf(STR, UWORD)
|
||||
in ArrayDatatypes -> targetType == this
|
||||
@@ -41,7 +43,8 @@ enum class DataType {
|
||||
this == other -> false
|
||||
this in ByteDatatypes -> false
|
||||
this in WordDatatypes -> other in ByteDatatypes
|
||||
this== STR && other== UWORD || this== UWORD && other== STR -> false
|
||||
this == LONG -> other in ByteDatatypes+WordDatatypes
|
||||
this == STR && other == UWORD || this == UWORD && other == STR -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
@@ -123,11 +126,11 @@ enum class BranchCondition {
|
||||
|
||||
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, 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 IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG)
|
||||
val IntegerDatatypes = IntegerDatatypesNoBool + DataType.BOOL
|
||||
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT)
|
||||
val NumericDatatypes = NumericDatatypesNoBool + DataType.BOOL
|
||||
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.LONG, DataType.FLOAT)
|
||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL)
|
||||
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||
val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT)
|
||||
|
||||
@@ -3,8 +3,6 @@ package prog8.code.core
|
||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
val name: String
|
||||
val machine: IMachineDefinition
|
||||
val supportedEncodings: Set<Encoding>
|
||||
val defaultEncoding: Encoding
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||
|
||||
@@ -9,6 +9,8 @@ enum class Encoding(val prefix: String) {
|
||||
}
|
||||
|
||||
interface IStringEncoding {
|
||||
val defaultEncoding: Encoding
|
||||
|
||||
fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||
fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package prog8.code.core
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import java.text.Normalizer
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.readText
|
||||
|
||||
@@ -94,7 +95,7 @@ sealed class SourceCode {
|
||||
val normalized = path.normalize()
|
||||
origin = relative(normalized).toString()
|
||||
try {
|
||||
text = normalized.readText()
|
||||
text = Normalizer.normalize(normalized.readText(), Normalizer.Form.NFC)
|
||||
name = normalized.toFile().nameWithoutExtension
|
||||
} catch (nfx: java.nio.file.NoSuchFileException) {
|
||||
throw NoSuchFileException(normalized.toFile()).also { it.initCause(nfx) }
|
||||
@@ -126,7 +127,7 @@ sealed class SourceCode {
|
||||
)
|
||||
}
|
||||
val stream = object {}.javaClass.getResourceAsStream(normalized)
|
||||
text = stream!!.reader().use { it.readText() }
|
||||
text = stream!!.reader().use { Normalizer.normalize(it.readText(), Normalizer.Form.NFC) }
|
||||
name = Path(pathString).toFile().nameWithoutExtension
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import prog8.code.target.atari.AtariMachineDefinition
|
||||
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
||||
override val name = NAME
|
||||
override val machine = AtariMachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.ATASCII)
|
||||
override val defaultEncoding = Encoding.ATASCII
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -11,7 +11,6 @@ import prog8.code.target.cbm.CbmMemorySizer
|
||||
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val machine = C128MachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -11,7 +11,6 @@ import prog8.code.target.cbm.CbmMemorySizer
|
||||
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val machine = C64MachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -11,7 +11,6 @@ import prog8.code.target.cx16.CX16MachineDefinition
|
||||
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val machine = CX16MachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -10,6 +10,8 @@ import prog8.code.target.cbm.PetsciiEncoding
|
||||
|
||||
|
||||
object Encoder: IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
val coded = when(encoding) {
|
||||
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
|
||||
|
||||
@@ -11,7 +11,6 @@ import prog8.code.target.pet.PETMachineDefinition
|
||||
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val machine = PETMachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -6,7 +6,6 @@ import prog8.code.target.virtual.VirtualMachineDefinition
|
||||
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
||||
override val name = NAME
|
||||
override val machine = VirtualMachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.ISO)
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -12,7 +12,7 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4 // 32-bits floating point
|
||||
override val FLOAT_MEM_SIZE = 8 // 64-bits double
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not actually used
|
||||
@@ -22,8 +22,8 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
// little endian binary representation
|
||||
val bits = num.toFloat().toBits().toUInt()
|
||||
val hexStr = bits.toString(16).padStart(8, '0')
|
||||
val bits = num.toDouble().toBits().toULong()
|
||||
val hexStr = bits.toString(16).padStart(16, '0')
|
||||
val parts = hexStr.chunked(2).map { "\$" + it }
|
||||
return parts.joinToString(", ")
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ dependencies {
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
||||
@@ -3107,6 +3107,17 @@ $repeatLabel""")
|
||||
return "prog8_label_asm_${generatedLabelSequenceNumber}_$postfix"
|
||||
}
|
||||
|
||||
fun assignConstFloatToPointerAY(number: PtNumber) {
|
||||
val floatConst = allocator.getFloatAsmConst(number.number)
|
||||
out("""
|
||||
pha
|
||||
lda #<$floatConst
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>$floatConst
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,8 +47,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"reverse" -> funcReverse(fcall)
|
||||
"memory" -> funcMemory(fcall, discardResult, resultRegister)
|
||||
"peekw" -> funcPeekW(fcall, resultRegister)
|
||||
"peekf" -> funcPeekF(fcall, resultRegister)
|
||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||
"pokew" -> funcPokeW(fcall)
|
||||
"pokef" -> funcPokeF(fcall)
|
||||
"pokemon" -> {
|
||||
val memread = PtMemoryByte(fcall.position)
|
||||
memread.add(fcall.args[0])
|
||||
@@ -81,26 +83,32 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"rsave" -> funcRsave()
|
||||
"rrestore" -> funcRrestore()
|
||||
"cmp" -> funcCmp(fcall)
|
||||
"callfar" -> funcCallFar(fcall)
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(fcall)
|
||||
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE)
|
||||
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD)
|
||||
"callfar" -> funcCallFar(fcall, resultRegister)
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
|
||||
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
|
||||
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
|
||||
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
|
||||
}
|
||||
|
||||
return BuiltinFunctions.getValue(fcall.name).returnType
|
||||
}
|
||||
|
||||
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType) {
|
||||
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType, resultRegister: RegisterOrPair?) {
|
||||
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
|
||||
when (resultType) {
|
||||
DataType.UBYTE -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A)
|
||||
asmgen.out(" tay | jsr math.multiply_bytes")
|
||||
if(resultRegister!=null) {
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), CpuRegister.A, false, false)
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.out(" jsr math.square")
|
||||
if(resultRegister!=null) {
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("optimized square only for integer types")
|
||||
@@ -140,9 +148,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
sty $remainderVar+1""")
|
||||
}
|
||||
|
||||
private fun funcStringCompare(fcall: PtBuiltinFunctionCall) {
|
||||
private fun funcStringCompare(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
asmgen.assignWordOperandsToAYAndVar(fcall.args[0], fcall.args[1], "P8ZP_SCRATCH_W2")
|
||||
asmgen.out(" jsr prog8_lib.strcmp_mem")
|
||||
if(resultRegister!=null) {
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), CpuRegister.A, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcRsave() {
|
||||
@@ -182,7 +193,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
plp""")
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: PtBuiltinFunctionCall) {
|
||||
private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
|
||||
@@ -196,6 +207,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
// note that by convention the values in A+Y registers are now the return value of the call.
|
||||
if(resultRegister!=null) {
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCmp(fcall: PtBuiltinFunctionCall) {
|
||||
@@ -752,6 +766,41 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPokeF(fcall: PtBuiltinFunctionCall) {
|
||||
when(val number = fcall.args[1]) {
|
||||
is PtIdentifier -> {
|
||||
val varName = asmgen.asmVariableName(number)
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda #<$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>$varName
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.assignConstFloatToPointerAY(number)
|
||||
}
|
||||
else -> {
|
||||
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
|
||||
asmgen.assignExpressionTo(fcall.args[1],
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position, tempvar, null, null, null, null))
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda #<$tempvar
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>$tempvar
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPokeW(fcall: PtBuiltinFunctionCall) {
|
||||
when(val addrExpr = fcall.args[0]) {
|
||||
is PtNumber -> {
|
||||
@@ -808,6 +857,15 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" jsr prog8_lib.func_pokew")
|
||||
}
|
||||
|
||||
private fun funcPeekF(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
asmgen.out(" jsr floats.MOVFM")
|
||||
if(resultRegister!=null) {
|
||||
assignAsmGen.assignFAC1float(
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position, null, null, null, resultRegister, null))
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPeekW(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
fun fallback() {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||
|
||||
@@ -2612,19 +2612,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
ldy #<${target.asmVarname}
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1""")
|
||||
val constIndex = target.array!!.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
asmgen.out(" lda #$constIndex")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
|
||||
asmgen.out(" lda $asmvarname")
|
||||
}
|
||||
asmgen.out(" jsr floats.set_array_float_from_fac1")
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
jsr floats.set_array_float_from_fac1""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
@@ -2647,21 +2641,19 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1""")
|
||||
val constIndex = target.array!!.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
asmgen.out(" lda #$constIndex")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
|
||||
asmgen.out(" lda $asmvarname")
|
||||
}
|
||||
asmgen.out(" jsr floats.set_array_float")
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
jsr floats.set_array_float""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
@@ -2687,23 +2679,17 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #<$sourceName
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy #>$sourceName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #<${target.asmVarname}
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1""")
|
||||
val constIndex = target.array!!.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
asmgen.out(" lda #$constIndex")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
|
||||
asmgen.out(" lda $asmvarname")
|
||||
}
|
||||
asmgen.out(" jsr floats.set_array_float")
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
jsr floats.set_array_float""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
@@ -2966,9 +2952,21 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
storeRegisterAInMemoryAddress(target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if(assignAsWord)
|
||||
TODO("assign register byte as word into Array not yet supported")
|
||||
assignRegisterByteToByteArray(target, register)
|
||||
if(assignAsWord) {
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> asmgen.out(" txa")
|
||||
CpuRegister.Y -> asmgen.out(" tya")
|
||||
}
|
||||
if(extendWord) {
|
||||
asmgen.signExtendAYlsb(if(target.datatype in SignedDatatypes) DataType.BYTE else DataType.UBYTE)
|
||||
} else {
|
||||
asmgen.out(" ldy #0")
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
} else {
|
||||
assignRegisterByteToByteArray(target, register)
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(register) {
|
||||
@@ -3138,6 +3136,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
throw AssemblyError("cannot assign byte to split word array here ${target.position}")
|
||||
|
||||
if(assignsIndexedPointerVar(target)) {
|
||||
// assign byte to a byte array via an uword pointervariable instead of a regular array variable
|
||||
if (target.constArrayIndexValue!=null) {
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
@@ -3162,17 +3161,33 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
CpuRegister.X -> asmgen.out(" txa")
|
||||
CpuRegister.Y -> asmgen.out(" tya")
|
||||
}
|
||||
val indexVar = target.array.index as PtIdentifier
|
||||
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
|
||||
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta (${target.asmVarname}),y")
|
||||
val indexVar = target.array.index as? PtIdentifier
|
||||
if(indexVar!=null) {
|
||||
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
|
||||
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta (${target.asmVarname}),y")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy ${target.asmVarname}
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy ${target.asmVarname}+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy ${asmgen.asmVariableName(indexVar)}
|
||||
sta (P8ZP_SCRATCH_W1),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy ${target.asmVarname}
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy ${target.asmVarname}+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy ${asmgen.asmVariableName(indexVar)}
|
||||
sta (P8ZP_SCRATCH_W1),y""")
|
||||
asmgen.saveRegisterStack(register, false)
|
||||
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
|
||||
asmgen.out(" sta (${target.asmVarname}),y")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldx ${target.asmVarname}
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx ${target.asmVarname}+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
sta (P8ZP_SCRATCH_W1),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -3624,37 +3639,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val constIndex = target.array!!.index.asConstInteger()
|
||||
if (constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out("""
|
||||
stz ${target.asmVarname}+$indexValue
|
||||
stz ${target.asmVarname}+$indexValue+1
|
||||
stz ${target.asmVarname}+$indexValue+2
|
||||
stz ${target.asmVarname}+$indexValue+3
|
||||
stz ${target.asmVarname}+$indexValue+4
|
||||
""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta ${target.asmVarname}+$indexValue
|
||||
sta ${target.asmVarname}+$indexValue+1
|
||||
sta ${target.asmVarname}+$indexValue+2
|
||||
sta ${target.asmVarname}+$indexValue+3
|
||||
sta ${target.asmVarname}+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda $asmvarname
|
||||
jsr floats.set_0_array_float
|
||||
""")
|
||||
}
|
||||
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
|
||||
asmgen.out("""
|
||||
ldy #<${target.asmVarname}
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy #>${target.asmVarname}
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
jsr floats.set_0_array_float""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
@@ -3681,33 +3672,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val arrayVarName = target.asmVarname
|
||||
val constIndex = target.array!!.index.asConstInteger()
|
||||
if (constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
|
||||
asmgen.out("""
|
||||
lda #<$constFloat
|
||||
ldy #>$constFloat
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<($arrayVarName+$indexValue)
|
||||
ldy #>($arrayVarName+$indexValue)
|
||||
jsr floats.copy_float""")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
|
||||
asmgen.out("""
|
||||
lda #<${constFloat}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${constFloat}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<${arrayVarName}
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>${arrayVarName}
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda $asmvarname
|
||||
jsr floats.set_array_float
|
||||
""")
|
||||
}
|
||||
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
|
||||
asmgen.out("""
|
||||
ldy #<${constFloat}
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy #>${constFloat}
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #<${target.asmVarname}
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy #>${target.asmVarname}
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
jsr floats.set_array_float
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
|
||||
+54
-22
@@ -1192,8 +1192,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
}
|
||||
"&" -> asmgen.out(" lda $name | and #$value | sta $name")
|
||||
"|" -> asmgen.out(" lda $name | ora #$value | sta $name")
|
||||
"&" -> immediateAndInplace(name, value)
|
||||
"|" -> immediateOrInplace(name, value)
|
||||
"^" -> asmgen.out(" lda $name | eor #$value | sta $name")
|
||||
"==" -> {
|
||||
asmgen.out("""
|
||||
@@ -1314,6 +1314,22 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun immediateAndInplace(name: String, value: Int) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
asmgen.out(" lda #${value xor 255} | trb $name") // reset bit
|
||||
} else {
|
||||
asmgen.out(" lda $name | and #$value | sta $name")
|
||||
}
|
||||
}
|
||||
|
||||
private fun immediateOrInplace(name: String, value: Int) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02) && ((value and (value-1))==0)) {
|
||||
asmgen.out(" lda #$value | tsb $name") // set bit
|
||||
} else {
|
||||
asmgen.out(" lda $name | ora #$value | sta $name")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplacemodificationWordWithMemread(name: String, dt: DataType, operator: String, memread: PtMemoryByte) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
@@ -1611,7 +1627,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" lda $name+1 | and #>$value | sta $name+1")
|
||||
}
|
||||
value < 0x0100 -> {
|
||||
asmgen.out(" lda $name | and #$value | sta $name")
|
||||
immediateAndInplace(name, value)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name+1")
|
||||
else
|
||||
@@ -1624,7 +1640,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
when {
|
||||
value == 0 -> {}
|
||||
value and 255 == 0 -> asmgen.out(" lda $name+1 | ora #>$value | sta $name+1")
|
||||
value < 0x0100 -> asmgen.out(" lda $name | ora #$value | sta $name")
|
||||
value < 0x0100 -> immediateOrInplace(name, value)
|
||||
else -> asmgen.out(" lda $name | ora #<$value | sta $name | lda $name+1 | ora #>$value | sta $name+1")
|
||||
}
|
||||
}
|
||||
@@ -2728,16 +2744,23 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
val constValueName = allocator.getFloatAsmConst(value)
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
if (value == 0.0)
|
||||
return
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr floats.MOVFM
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr floats.FADD
|
||||
""")
|
||||
when (value) {
|
||||
0.0 -> return
|
||||
0.5 -> asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr floats.MOVFM
|
||||
jsr floats.FADDH
|
||||
""")
|
||||
else -> asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr floats.MOVFM
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr floats.FADD
|
||||
""")
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if (value == 0.0)
|
||||
@@ -2753,14 +2776,23 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
"*" -> {
|
||||
// assume that code optimization is already done on the AST level for special cases such as 0, 1, 2...
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr floats.MOVFM
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr floats.FMULT
|
||||
""")
|
||||
if(value==10.0) {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr floats.MOVFM
|
||||
jsr floats.MUL10
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr floats.MOVFM
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr floats.FMULT
|
||||
""")
|
||||
}
|
||||
}
|
||||
"/" -> {
|
||||
if (value == 0.0)
|
||||
|
||||
@@ -18,6 +18,8 @@ internal object DummyMemsizer : IMemSizer {
|
||||
}
|
||||
|
||||
internal object DummyStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ dependencies {
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
||||
@@ -29,10 +29,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"msb" -> funcMsb(call)
|
||||
"lsb" -> funcLsb(call)
|
||||
"memory" -> funcMemory(call)
|
||||
"peek" -> funcPeek(call)
|
||||
"peekw" -> funcPeekW(call)
|
||||
"poke" -> funcPoke(call)
|
||||
"pokew" -> funcPokeW(call)
|
||||
"peek" -> funcPeek(call, IRDataType.BYTE)
|
||||
"peekw" -> funcPeek(call, IRDataType.WORD)
|
||||
"peekf" -> funcPeek(call, IRDataType.FLOAT)
|
||||
"poke" -> funcPoke(call, IRDataType.BYTE)
|
||||
"pokew" -> funcPoke(call, IRDataType.WORD)
|
||||
"pokef" -> funcPoke(call, IRDataType.FLOAT)
|
||||
"pokemon" -> funcPokemon(call)
|
||||
"mkword" -> funcMkword(call)
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
|
||||
@@ -440,116 +442,93 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcPokeW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcPoke(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, address = address)
|
||||
it += IRInstruction(Opcode.STOREZM, dt, address = address)
|
||||
}
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg1 = tr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
val tr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = tr.resultReg, address = address)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, dt, fpReg1 = tr.resultFpReg, address = address)
|
||||
}
|
||||
} else {
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, dt, reg1 = tr.resultReg, address = address)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addToResult(result, valueTr, -1, valueTr.resultFpReg)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = valueTr.resultFpReg)
|
||||
}
|
||||
} else {
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPoke(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcPeek(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
return if(dt==IRDataType.FLOAT) {
|
||||
if(call.args[0] is PtNumber) {
|
||||
val resultFpRegister = codeGen.registers.nextFreeFloat()
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = resultFpRegister, address = address)
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultFpReg = codeGen.registers.nextFreeFloat()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = tr.resultReg)
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = resultFpReg)
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
|
||||
}
|
||||
} else {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
val tr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
|
||||
it += IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address)
|
||||
}
|
||||
ExpressionCodeResult(result, dt, resultRegister, -1)
|
||||
} else {
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
|
||||
it += IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg)
|
||||
}
|
||||
ExpressionCodeResult(result, dt, resultReg, -1)
|
||||
}
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPeekW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
return if(call.args[0] is PtNumber) {
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, address = address)
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg)
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPeek(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
return if(call.args[0] is PtNumber) {
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = address)
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg)
|
||||
}
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPokemon(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
|
||||
@@ -17,6 +17,8 @@ internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType
|
||||
if(resultReg!=-1) require(resultFpReg==-1)
|
||||
if(resultFpReg!=-1) require(resultReg==-1)
|
||||
}
|
||||
|
||||
fun lastInstruction() = chunks.last().instructions.last()
|
||||
}
|
||||
|
||||
internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
@@ -31,7 +33,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
val resultFpRegister = codeGen.registers.nextFreeFloat()
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number.toFloat())
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number)
|
||||
ExpressionCodeResult(code, vmDt,-1, resultFpRegister)
|
||||
}
|
||||
else {
|
||||
@@ -711,7 +713,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
val factor = constFactorRight.number.toFloat()
|
||||
val factor = constFactorRight.number
|
||||
result += codeGen.divideByConstFloat(tr.resultFpReg, factor)
|
||||
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
} else {
|
||||
@@ -767,13 +769,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
return if(constFactorLeft!=null) {
|
||||
val tr = translateExpression(binExpr.right)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
val factor = constFactorLeft.number.toFloat()
|
||||
val factor = constFactorLeft.number
|
||||
result += codeGen.multiplyByConstFloat(tr.resultFpReg, factor)
|
||||
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
} else if(constFactorRight!=null) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
val factor = constFactorRight.number.toFloat()
|
||||
val factor = constFactorRight.number
|
||||
result += codeGen.multiplyByConstFloat(tr.resultFpReg, factor)
|
||||
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
} else {
|
||||
@@ -821,7 +823,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
return if(binExpr.right is PtNumber) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number.toFloat()), null)
|
||||
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
|
||||
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
} else {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
@@ -876,7 +878,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
return if(binExpr.right is PtNumber) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number.toFloat()), null)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
|
||||
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
} else {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
@@ -948,7 +950,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val constFactorRight = operand as? PtNumber
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
|
||||
val factor = constFactorRight.number.toFloat()
|
||||
val factor = constFactorRight.number
|
||||
result += codeGen.divideByConstFloatInplace(knownAddress, symbol, factor)
|
||||
} else {
|
||||
val tr = translateExpression(operand)
|
||||
@@ -997,7 +999,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val constFactorRight = operand as? PtNumber
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
if(constFactorRight!=null) {
|
||||
val factor = constFactorRight.number.toFloat()
|
||||
val factor = constFactorRight.number
|
||||
result += codeGen.multiplyByConstFloatInplace(knownAddress, symbol, factor)
|
||||
} else {
|
||||
val tr = translateExpression(operand)
|
||||
@@ -1322,7 +1324,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
// in-place modify a memory location
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number)
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
|
||||
@@ -1333,7 +1335,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
// in-place modify a symbol (variable)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number)
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
|
||||
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
|
||||
|
||||
@@ -283,15 +283,10 @@ class IRCodeGen(
|
||||
else -> TODO("missing codegen for $node")
|
||||
}
|
||||
|
||||
chunks.forEach { chunk ->
|
||||
require(chunk.isNotEmpty() || chunk.label != null) {
|
||||
"chunk should have instructions and/or a label"
|
||||
}
|
||||
}
|
||||
val nonEmptyChunks = chunks.filter { it.isNotEmpty() || it.label != null }
|
||||
nonEmptyChunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(node.position)
|
||||
|
||||
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(node.position)
|
||||
|
||||
return chunks
|
||||
return nonEmptyChunks
|
||||
}
|
||||
|
||||
private fun readBinaryData(node: PtIncludeBinary): Collection<UByte> {
|
||||
@@ -470,7 +465,6 @@ class IRCodeGen(
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
|
||||
result += IRCodeChunk(loopLabel, null).also {
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = tmpReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
|
||||
}
|
||||
@@ -499,7 +493,10 @@ class IRCodeGen(
|
||||
result += translateNode(forLoop.statements)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexReg)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(iterableLength==256) 0 else iterableLength)
|
||||
if(iterableLength!=256) {
|
||||
// for length 256, the compare is actually against 0, which doesn't require a separate CMP instruction
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = iterableLength)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
}
|
||||
@@ -516,7 +513,10 @@ class IRCodeGen(
|
||||
result += translateNode(forLoop.statements)
|
||||
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(lengthBytes==256) 0 else lengthBytes)
|
||||
if(lengthBytes!=256) {
|
||||
// for length 256, the compare is actually against 0, which doesn't require a separate CMP instruction
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = lengthBytes)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
}
|
||||
@@ -705,23 +705,23 @@ class IRCodeGen(
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun multiplyByConstFloat(fpReg: Int, factor: Float): IRCodeChunk {
|
||||
internal fun multiplyByConstFloat(fpReg: Int, factor: Double): IRCodeChunk {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1f)
|
||||
if(factor==1.0)
|
||||
return code
|
||||
code += if(factor==0f) {
|
||||
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0f)
|
||||
code += if(factor==0.0) {
|
||||
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0.0)
|
||||
} else {
|
||||
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun multiplyByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Float): IRCodeChunk {
|
||||
internal fun multiplyByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Double): IRCodeChunk {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1f)
|
||||
if(factor==1.0)
|
||||
return code
|
||||
if(factor==0f) {
|
||||
if(factor==0.0) {
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, address = knownAddress)
|
||||
else
|
||||
@@ -802,25 +802,25 @@ class IRCodeGen(
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun divideByConstFloat(fpReg: Int, factor: Float): IRCodeChunk {
|
||||
internal fun divideByConstFloat(fpReg: Int, factor: Double): IRCodeChunk {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1f)
|
||||
if(factor==1.0)
|
||||
return code
|
||||
code += if(factor==0f) {
|
||||
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = Float.MAX_VALUE)
|
||||
code += if(factor==0.0) {
|
||||
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = Double.MAX_VALUE)
|
||||
} else {
|
||||
IRInstruction(Opcode.DIVS, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun divideByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Float): IRCodeChunk {
|
||||
internal fun divideByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Double): IRCodeChunk {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1f)
|
||||
if(factor==1.0)
|
||||
return code
|
||||
if(factor==0f) {
|
||||
if(factor==0.0) {
|
||||
val maxvalueReg = registers.nextFreeFloat()
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = maxvalueReg, immediateFp = Float.MAX_VALUE)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = maxvalueReg, immediateFp = Double.MAX_VALUE)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = maxvalueReg, address = knownAddress)
|
||||
else
|
||||
@@ -976,7 +976,6 @@ class IRCodeGen(
|
||||
fpReg2 = rightTr.resultFpReg
|
||||
)
|
||||
when(condition.operator) {
|
||||
// TODO: the converted list of operators
|
||||
"==" -> {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
|
||||
it += branchInstr(goto, Opcode.BSTEQ)
|
||||
@@ -986,7 +985,6 @@ class IRCodeGen(
|
||||
it += branchInstr(goto, Opcode.BSTNE)
|
||||
}
|
||||
else -> {
|
||||
// TODO: the old list of operators, still to be converted
|
||||
val gotoOpcode = when (condition.operator) {
|
||||
"<" -> Opcode.BLTS
|
||||
">" -> Opcode.BGTS
|
||||
@@ -1016,7 +1014,6 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO use this everywhere
|
||||
private fun branchInstr(goto: PtJump, branchOpcode: Opcode) = if (goto.address != null)
|
||||
IRInstruction(branchOpcode, address = goto.address?.toInt())
|
||||
else if (goto.generatedLabel != null)
|
||||
@@ -1034,18 +1031,19 @@ class IRCodeGen(
|
||||
val condition = ifElse.condition as PtBinaryExpression
|
||||
val leftTr = expressionEval.translateExpression(condition.left)
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
val requireCompareZero = leftTr.lastInstruction().opcode !in OpcodesThatSetStatusbits
|
||||
when(condition.operator) {
|
||||
// TODO: converted list of operators
|
||||
"==" -> {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
|
||||
if(requireCompareZero)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
|
||||
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
|
||||
}
|
||||
"!=" -> {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
|
||||
if(requireCompareZero)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
|
||||
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
|
||||
}
|
||||
else -> {
|
||||
// TODO: to-be converted operators
|
||||
val opcode = when (condition.operator) {
|
||||
"<" -> if (signed) Opcode.BLTS else Opcode.BLT
|
||||
">" -> if (signed) Opcode.BGTS else Opcode.BGT
|
||||
@@ -1072,12 +1070,13 @@ class IRCodeGen(
|
||||
) {
|
||||
val condition = ifElse.condition as? PtBinaryExpression
|
||||
if(condition==null) {
|
||||
val tr = expressionEval.translateExpression(ifElse.condition)
|
||||
result += tr.chunks
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, irDtLeft, reg1 = tr.resultReg, immediate = 0)
|
||||
it += branchInstr(goto, Opcode.BSTNE)
|
||||
}
|
||||
throw AssemblyError("expected condition")
|
||||
// val tr = expressionEval.translateExpression(ifElse.condition)
|
||||
// result += tr.chunks
|
||||
// result += IRCodeChunk(null, null).also {
|
||||
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1 = tr.resultReg, immediate = 0) // was redundant CMP most likely
|
||||
// it += branchInstr(goto, Opcode.BSTNE)
|
||||
// }
|
||||
} else {
|
||||
val leftTr = expressionEval.translateExpression(condition.left)
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
@@ -1085,7 +1084,6 @@ class IRCodeGen(
|
||||
if(number!=null) {
|
||||
val firstReg = leftTr.resultReg
|
||||
when(condition.operator) {
|
||||
// TODO: the converted operators
|
||||
"==" -> {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = firstReg, immediate = number), null)
|
||||
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
|
||||
@@ -1095,7 +1093,6 @@ class IRCodeGen(
|
||||
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
|
||||
}
|
||||
else -> {
|
||||
// TODO: to-be converted operators
|
||||
val opcode = when (condition.operator) {
|
||||
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
|
||||
">" -> if(signed) Opcode.BGTS else Opcode.BGT
|
||||
@@ -1187,7 +1184,7 @@ class IRCodeGen(
|
||||
addToResult(result, leftTr, -1, leftTr.resultFpReg)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val rightFpReg = registers.nextFreeFloat()
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = rightFpReg, immediateFp = 0f)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = rightFpReg, immediateFp = 0.0)
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightFpReg)
|
||||
}
|
||||
when (condition.operator) {
|
||||
@@ -1267,33 +1264,6 @@ class IRCodeGen(
|
||||
val condition = ifElse.condition as? PtBinaryExpression
|
||||
if(condition==null) {
|
||||
throw AssemblyError("if-else condition is not a binaryexpression, should have been converted?")
|
||||
// if(irDtLeft==IRDataType.FLOAT)
|
||||
// throw AssemblyError("condition value should not be float")
|
||||
// val tr = expressionEval.translateExpression(ifElse.condition)
|
||||
// result += tr.chunks
|
||||
// if(ifElse.elseScope.children.isNotEmpty()) {
|
||||
// val elseLabel = createLabelName()
|
||||
// result += IRCodeChunk(null, null).also {
|
||||
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1=tr.resultReg, immediate = 0)
|
||||
// it += IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
|
||||
// TODO("test this")
|
||||
// }
|
||||
// result += translateNode(ifElse.ifScope)
|
||||
// val afterIfLabel = createLabelName()
|
||||
// addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
|
||||
// result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
|
||||
// result += IRCodeChunk(afterIfLabel, null)
|
||||
// } else {
|
||||
// val afterIfLabel = createLabelName()
|
||||
// result += IRCodeChunk(null, null).also {
|
||||
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1=tr.resultReg, immediate = 0)
|
||||
// it += IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel)
|
||||
// TODO("test this")
|
||||
// }
|
||||
// result += translateNode(ifElse.ifScope)
|
||||
// result += IRCodeChunk(afterIfLabel, null)
|
||||
// }
|
||||
// return result
|
||||
} else {
|
||||
if (irDtLeft == IRDataType.FLOAT) {
|
||||
val leftTr = expressionEval.translateExpression(condition.left)
|
||||
@@ -1641,10 +1611,7 @@ class IRCodeGen(
|
||||
addToResult(result, countTr, countTr.resultReg, -1)
|
||||
if(constIntValue(repeat.count)==null) {
|
||||
// check if the counter is already zero
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, irDt, reg1=countTr.resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
|
||||
}
|
||||
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
|
||||
@@ -87,7 +87,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
/*
|
||||
Empty Code chunk with label ->
|
||||
If next chunk has no label -> move label to next chunk, remove original
|
||||
If next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: merge both labels into 1)
|
||||
If next chunk has label -> label name should be the same, remove original. Otherwise merge both labels into 1.
|
||||
If is last chunk -> keep chunk in place because of the label.
|
||||
Empty Code chunk without label ->
|
||||
should not have been generated! ERROR.
|
||||
@@ -96,6 +96,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
|
||||
val relabelChunks = mutableListOf<Pair<Int, String>>()
|
||||
val removeChunks = mutableListOf<Int>()
|
||||
val replaceLabels = mutableMapOf<String, String>()
|
||||
|
||||
sub.chunks.withIndex().forEach { (index, chunk) ->
|
||||
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
|
||||
@@ -109,10 +110,18 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
relabelChunks += Pair(index + 1, chunk.label!!)
|
||||
removeChunks += index
|
||||
} else {
|
||||
if (chunk.label == nextchunk.label)
|
||||
removeChunks += index
|
||||
else {
|
||||
// TODO: merge labels on same chunk
|
||||
// merge both labels into 1 except if this is the label chunk at the start of the subroutine
|
||||
if(index>0) {
|
||||
if (chunk.label == nextchunk.label)
|
||||
removeChunks += index
|
||||
else {
|
||||
removeChunks += index
|
||||
replaceLabels[chunk.label!!] = nextchunk.label!!
|
||||
replaceLabels.entries.forEach { (key, value) ->
|
||||
if (value == chunk.label)
|
||||
replaceLabels[key] = nextchunk.label!!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,6 +138,25 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
sub.chunks[index] = chunk
|
||||
}
|
||||
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
|
||||
|
||||
sub.chunks.forEach { chunk ->
|
||||
chunk.instructions.withIndex().forEach { (idx, instr) ->
|
||||
instr.labelSymbol?.let {
|
||||
if(instr.opcode in OpcodesThatBranch) {
|
||||
replaceLabels.forEach { (from, to) ->
|
||||
if (it == from) {
|
||||
chunk.instructions[idx] = instr.copy(labelSymbol = to)
|
||||
}
|
||||
else {
|
||||
val actualPrefix = "$from."
|
||||
if (it.startsWith(actualPrefix))
|
||||
chunk.instructions[idx] = instr.copy(labelSymbol = "$to.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun joinChunks(sub: IRSubroutine) {
|
||||
@@ -380,6 +408,9 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
}
|
||||
|
||||
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
return false
|
||||
|
||||
/*
|
||||
var changed = false
|
||||
indexedInstructions.forEach { (idx, ins) ->
|
||||
|
||||
@@ -393,5 +424,6 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
// ...
|
||||
}
|
||||
return changed
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,9 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
}
|
||||
|
||||
irprog.st.allVariables().forEach { stVar->
|
||||
val initValue = stVar.onetimeInitializationArrayValue
|
||||
if(initValue!=null && !initValue.isEmpty()) {
|
||||
irprog.st.allVariables().forEach { variable ->
|
||||
val initValue = variable.onetimeInitializationArrayValue
|
||||
if(!initValue.isNullOrEmpty()) {
|
||||
if(initValue.any {
|
||||
it.addressOfSymbol?.startsWith(blockLabel)==true
|
||||
})
|
||||
@@ -61,9 +61,6 @@ class IRUnusedCodeRemover(
|
||||
irprog.blocks.forEach { block ->
|
||||
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
|
||||
if(sub.isEmpty()) {
|
||||
if(!sub.position.file.startsWith(LIBRARYFILEPREFIX)) {
|
||||
errors.warn("unused subroutine ${sub.label}", sub.position)
|
||||
}
|
||||
block.children.remove(sub)
|
||||
irprog.st.removeTree(sub.label)
|
||||
numRemoved++
|
||||
|
||||
@@ -16,6 +16,8 @@ internal object DummyMemsizer : IMemSizer {
|
||||
}
|
||||
|
||||
internal object DummyStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@@ -53,9 +53,9 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
|
||||
test("remove jmp to label below") {
|
||||
val c1 = IRCodeChunk("main.start", null)
|
||||
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
|
||||
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
|
||||
val c2 = IRCodeChunk("label", null)
|
||||
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
|
||||
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2")
|
||||
c2 += IRInstruction(Opcode.NOP) // removed
|
||||
val c3 = IRCodeChunk("label2", null)
|
||||
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
|
||||
@@ -67,15 +67,13 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
irProg.chunks().size shouldBe 4
|
||||
irProg.chunks().size shouldBe 3
|
||||
irProg.chunks()[0].label shouldBe "main.start"
|
||||
irProg.chunks()[1].label shouldBe "label"
|
||||
irProg.chunks()[2].label shouldBe "label2"
|
||||
irProg.chunks()[3].label shouldBe "label3"
|
||||
irProg.chunks()[1].label shouldBe "label2"
|
||||
irProg.chunks()[2].label shouldBe "label3"
|
||||
irProg.chunks()[0].isEmpty() shouldBe true
|
||||
irProg.chunks()[1].isEmpty() shouldBe true
|
||||
irProg.chunks()[2].isEmpty() shouldBe false
|
||||
irProg.chunks()[3].isEmpty() shouldBe true
|
||||
irProg.chunks()[1].isEmpty() shouldBe false
|
||||
irProg.chunks()[2].isEmpty() shouldBe true
|
||||
val instr = irProg.chunks().flatMap { it.instructions }
|
||||
instr.size shouldBe 2
|
||||
instr[0].opcode shouldBe Opcode.JUMP
|
||||
|
||||
@@ -2,7 +2,6 @@ package prog8.optimizer
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.maySwapOperandOrder
|
||||
import prog8.ast.statements.ForLoop
|
||||
@@ -185,10 +184,12 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
} else if (leftBinExpr.operator=="/") {
|
||||
// (X / C2) * rightConst --> X * (rightConst/C2)
|
||||
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
if(expr.inferType(program).istype(DataType.FLOAT)) {
|
||||
// (X / C2) * rightConst --> X * (rightConst/C2) only valid for floating point
|
||||
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,7 +197,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
val c2 = leftBinExpr.right.constValue(program)
|
||||
if(c2!=null && leftBinExpr.operator=="/") {
|
||||
// (X / C1) / C2 --> X / (C1*C2)
|
||||
// NOTE: do not optimize (X * C1) / C2 --> X * (C1/C2) because this causes precision loss on integers
|
||||
// NOTE: do not optimize (X * C1) / C2 for integers, --> X * (C1/C2) because this causes precision loss on integers
|
||||
val constants = BinaryExpression(c2, "*", rightconst, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "/", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
@@ -387,7 +388,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
||||
else -> { /* nothing for floats, these are not allowed in for loops and will give an error elsewhere */ }
|
||||
}
|
||||
|
||||
return noModifications
|
||||
@@ -527,7 +528,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="*" && subExpr.operator=="/") {
|
||||
else if(expr.operator=="*" && subExpr.operator=="/" && subExpr.inferType(program).istype(DataType.FLOAT)) {
|
||||
// division optimizations only valid for floats
|
||||
if(leftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// C1*(C2/V) -> (C1*C2)/V
|
||||
|
||||
@@ -28,6 +28,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
errors.err("refused rounding of float to avoid loss of precision", decl.value!!.position)
|
||||
} else {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
declConstValue.linkParents(decl)
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
if (cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
||||
|
||||
@@ -34,85 +34,95 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine}
|
||||
if(!containsSubsOrVariables) {
|
||||
if(subroutine.statements.size==1 || (subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))) {
|
||||
// subroutine is possible candidate to be inlined
|
||||
subroutine.inline =
|
||||
when(val stmt=subroutine.statements[0]) {
|
||||
is Return -> {
|
||||
if(stmt.value is NumericLiteral)
|
||||
true
|
||||
else if(stmt.value==null)
|
||||
true
|
||||
else if (stmt.value is IdentifierReference) {
|
||||
makeFullyScoped(stmt.value as IdentifierReference)
|
||||
true
|
||||
} else if(stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size<=1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
|
||||
when (stmt.value) {
|
||||
is BuiltinFunctionCall -> {
|
||||
makeFullyScoped(stmt.value as BuiltinFunctionCall)
|
||||
true
|
||||
if(subroutine !== program.entrypoint) {
|
||||
// subroutine is possible candidate to be inlined
|
||||
subroutine.inline =
|
||||
when (val stmt = subroutine.statements[0]) {
|
||||
is Return -> {
|
||||
if (stmt.value is NumericLiteral)
|
||||
true
|
||||
else if (stmt.value == null)
|
||||
true
|
||||
else if (stmt.value is IdentifierReference) {
|
||||
makeFullyScoped(stmt.value as IdentifierReference)
|
||||
true
|
||||
} else if (stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size <= 1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
|
||||
when (stmt.value) {
|
||||
is BuiltinFunctionCall -> {
|
||||
makeFullyScoped(stmt.value as BuiltinFunctionCall)
|
||||
true
|
||||
}
|
||||
|
||||
is FunctionCallExpression -> {
|
||||
makeFullyScoped(stmt.value as FunctionCallExpression)
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
is FunctionCallExpression -> {
|
||||
makeFullyScoped(stmt.value as FunctionCallExpression)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
} else
|
||||
false
|
||||
}
|
||||
is Assignment -> {
|
||||
if(stmt.value.isSimple) {
|
||||
val targetInline =
|
||||
if(stmt.target.identifier!=null) {
|
||||
makeFullyScoped(stmt.target.identifier!!)
|
||||
true
|
||||
} else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
|
||||
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
|
||||
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
|
||||
true
|
||||
} else
|
||||
false
|
||||
val valueInline =
|
||||
if(stmt.value is IdentifierReference) {
|
||||
makeFullyScoped(stmt.value as IdentifierReference)
|
||||
true
|
||||
} else if((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
|
||||
if((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
|
||||
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
|
||||
true
|
||||
} else
|
||||
false
|
||||
targetInline || valueInline
|
||||
} else
|
||||
false
|
||||
}
|
||||
is BuiltinFunctionCallStatement -> {
|
||||
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
|
||||
if(inline)
|
||||
makeFullyScoped(stmt)
|
||||
inline
|
||||
}
|
||||
is FunctionCallStatement -> {
|
||||
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
|
||||
if(inline)
|
||||
makeFullyScoped(stmt)
|
||||
inline
|
||||
}
|
||||
is PostIncrDecr -> {
|
||||
if(stmt.target.identifier!=null) {
|
||||
makeFullyScoped(stmt.target.identifier!!)
|
||||
true
|
||||
} else
|
||||
false
|
||||
}
|
||||
else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
|
||||
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
|
||||
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
|
||||
true
|
||||
} else
|
||||
false
|
||||
|
||||
is Assignment -> {
|
||||
if (stmt.value.isSimple) {
|
||||
val targetInline =
|
||||
if (stmt.target.identifier != null) {
|
||||
makeFullyScoped(stmt.target.identifier!!)
|
||||
true
|
||||
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
|
||||
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
|
||||
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
|
||||
true
|
||||
} else
|
||||
false
|
||||
val valueInline =
|
||||
if (stmt.value is IdentifierReference) {
|
||||
makeFullyScoped(stmt.value as IdentifierReference)
|
||||
true
|
||||
} else if ((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
|
||||
if ((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
|
||||
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
|
||||
true
|
||||
} else
|
||||
false
|
||||
targetInline || valueInline
|
||||
} else
|
||||
false
|
||||
}
|
||||
|
||||
is BuiltinFunctionCallStatement -> {
|
||||
val inline =
|
||||
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
|
||||
if (inline)
|
||||
makeFullyScoped(stmt)
|
||||
inline
|
||||
}
|
||||
|
||||
is FunctionCallStatement -> {
|
||||
val inline =
|
||||
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
|
||||
if (inline)
|
||||
makeFullyScoped(stmt)
|
||||
inline
|
||||
}
|
||||
|
||||
is PostIncrDecr -> {
|
||||
if (stmt.target.identifier != null) {
|
||||
makeFullyScoped(stmt.target.identifier!!)
|
||||
true
|
||||
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
|
||||
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
|
||||
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
|
||||
true
|
||||
} else
|
||||
false
|
||||
}
|
||||
|
||||
is Jump -> true
|
||||
else -> false
|
||||
}
|
||||
is Jump -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine.inline && subroutine.statements.size>1) {
|
||||
@@ -134,16 +144,20 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
|
||||
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = BuiltinFunctionCallStatement(call.target.copy(), scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
if(scopedArgs.any()) {
|
||||
val scopedCall = BuiltinFunctionCallStatement(call.target.copy(), scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: FunctionCallStatement) {
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
if(scopedArgs.any()) {
|
||||
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +165,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
if(scopedArgs.any()) {
|
||||
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,8 +176,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
if(scopedArgs.any()) {
|
||||
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +188,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
when (it) {
|
||||
is NumericLiteral -> it.copy()
|
||||
is IdentifierReference -> {
|
||||
val scoped = (it.targetStatement(program)!! as INamedStatement).scopedName
|
||||
val target = it.targetStatement(program) ?: return emptyList()
|
||||
val scoped = (target as INamedStatement).scopedName
|
||||
IdentifierReference(scoped, it.position)
|
||||
}
|
||||
else -> throw InternalCompilerException("expected only number or identifier arg, otherwise too complex")
|
||||
|
||||
@@ -45,11 +45,7 @@ class StatementOptimizer(private val program: Program,
|
||||
mutableListOf(NumericLiteral(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val stringDecl = string.parent as VarDecl
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(functionCallStatement, chrout, parent),
|
||||
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
} else if (string.value.length == 2) {
|
||||
val firstTwoCharsEncoded = options.compTarget.encodeString(string.value.take(2), string.encoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
@@ -62,11 +58,9 @@ class StatementOptimizer(private val program: Program,
|
||||
mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val stringDecl = string.parent as VarDecl
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent),
|
||||
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
|
||||
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -77,6 +71,11 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = ifElse.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
errors.warn("condition is always ${constvalue.asBooleanValue}", ifElse.condition.position)
|
||||
}
|
||||
|
||||
// remove empty if statements
|
||||
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty())
|
||||
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
|
||||
@@ -93,17 +92,12 @@ class StatementOptimizer(private val program: Program,
|
||||
)
|
||||
}
|
||||
|
||||
val constvalue = ifElse.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
if(!ifElse.definingModule.isLibrary)
|
||||
errors.warn("condition is always true", ifElse.condition.position)
|
||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
|
||||
} else {
|
||||
// always false -> keep only else-part
|
||||
if(!ifElse.definingModule.isLibrary)
|
||||
errors.warn("condition is always false", ifElse.condition.position)
|
||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
|
||||
}
|
||||
}
|
||||
@@ -174,6 +168,70 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val loopvarDt = forLoop.loopVarDt(program)
|
||||
if(loopvarDt.istype(DataType.UWORD) || loopvarDt.istype(DataType.UBYTE)) {
|
||||
if (range != null && range.from.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==1.0) {
|
||||
val toBinExpr = range.to as? BinaryExpression
|
||||
if(toBinExpr!=null && toBinExpr.operator=="-" && toBinExpr.right.constValue(program)?.number==1.0) {
|
||||
// FOR var IN 0 TO X-1 .... ---> var=0, DO {... , var++} UNTIL var==X
|
||||
val pos = forLoop.position
|
||||
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", toBinExpr.left, pos)
|
||||
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
|
||||
forLoop.body.statements.add(incOne)
|
||||
val replacement = AnonymousScope(mutableListOf(
|
||||
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
|
||||
NumericLiteral.optimalNumeric(0.0, pos),
|
||||
AssignmentOrigin.OPTIMIZER, pos),
|
||||
UntilLoop(forLoop.body, condition, pos)
|
||||
), pos)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
|
||||
}
|
||||
|
||||
if(options.compTarget.name!=VMTarget.NAME) {
|
||||
// this optimization is not effective for the VM target.
|
||||
val toConst = range.to.constValue(program)
|
||||
if (toConst == null) {
|
||||
// FOR var in 0 TO X ... ---> var=0, REPEAT { ... , IF var==X break , var++ }
|
||||
val pos = forLoop.position
|
||||
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
|
||||
val breakCondition = IfElse(
|
||||
BinaryExpression(forLoop.loopVar, "==", range.to, pos),
|
||||
AnonymousScope(mutableListOf(Break(pos)), pos),
|
||||
AnonymousScope(mutableListOf(), pos),
|
||||
pos
|
||||
)
|
||||
forLoop.body.statements.add(breakCondition)
|
||||
forLoop.body.statements.add(incOne)
|
||||
val replacement = AnonymousScope(mutableListOf(
|
||||
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
|
||||
NumericLiteral.optimalNumeric(0.0, pos),
|
||||
AssignmentOrigin.OPTIMIZER, pos),
|
||||
RepeatLoop(null, forLoop.body, pos)
|
||||
), pos)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
|
||||
val fromExpr = range.from
|
||||
if(fromExpr.constValue(program)==null) {
|
||||
// FOR X = something DOWNTO 0 {...} --> X=something, DO { ... , X-- } UNTIL X=255 (or 65535 if uword)
|
||||
val pos = forLoop.position
|
||||
val checkValue = NumericLiteral(loopvarDt.getOr(DataType.UNDEFINED), if(loopvarDt.istype(DataType.UBYTE)) 255.0 else 65535.0, pos)
|
||||
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", checkValue, pos)
|
||||
val decOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "--", pos)
|
||||
forLoop.body.statements.add(decOne)
|
||||
val replacement = AnonymousScope(mutableListOf(
|
||||
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
|
||||
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
|
||||
UntilLoop(forLoop.body, condition, pos)
|
||||
), pos)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
return removals
|
||||
}
|
||||
if(!subroutine.definingModule.isLibrary && !subroutine.hasBeenInlined) {
|
||||
if(!subroutine.hasBeenInlined && subroutine.definingModule===program.toplevelModule) {
|
||||
errors.warn("unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
}
|
||||
if(!subroutine.inline) {
|
||||
@@ -139,13 +139,20 @@ class UnusedCodeRemover(private val program: Program,
|
||||
)
|
||||
} else if(assignment.value is IFunctionCall) {
|
||||
// replace the unused variable's initializer function call by a void
|
||||
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
|
||||
val fcall = assignment.value as IFunctionCall
|
||||
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, voidCall, parent),
|
||||
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
||||
)
|
||||
// but only if the vardecl immediately precedes it!
|
||||
if(singleUse.parent.parent === parent) {
|
||||
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
|
||||
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
|
||||
if(declIndex==singleUseIndex-1) {
|
||||
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
|
||||
val fcall = assignment.value as IFunctionCall
|
||||
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, voidCall, parent),
|
||||
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.warn("variable '${decl.name}' is unused but has non-trivial initialization assignment. Leaving this in but maybe it can be removed altogether", decl.position)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ dependencies {
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||
|
||||
testImplementation project(':intermediate')
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
|
||||
@@ -8,6 +8,13 @@ atari {
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
&uword COLCRS = 85
|
||||
&ubyte ROWCRS = 84
|
||||
|
||||
romsub $F24A = getchar() -> ubyte @A
|
||||
romsub $F2B0 = outchar(ubyte character @ A)
|
||||
romsub $F2FD = waitkey() -> ubyte @A
|
||||
|
||||
}
|
||||
|
||||
sys {
|
||||
@@ -206,6 +213,46 @@ _longcopy
|
||||
; no-op
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
; TODO
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Atari 800XL
|
||||
; Reference: https://www.atariarchives.org/mapping/appendix12.php
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -24,24 +25,22 @@ sub spc() {
|
||||
txt.chrout(' ')
|
||||
}
|
||||
|
||||
const uword COLCRS = 85
|
||||
const uword ROWCRS = 84
|
||||
sub column(ubyte col) {
|
||||
; ---- set the cursor on the given column (starting with 0) on the current line
|
||||
pokew(COLCRS, col as uword)
|
||||
atari.COLCRS = col
|
||||
}
|
||||
|
||||
sub get_column() -> ubyte {
|
||||
return peekw(COLCRS) as ubyte
|
||||
return atari.COLCRS as ubyte
|
||||
}
|
||||
|
||||
sub row(ubyte rownum) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
@(ROWCRS) = rownum
|
||||
atari.ROWCRS = rownum
|
||||
}
|
||||
|
||||
sub get_row() -> ubyte {
|
||||
return @(ROWCRS)
|
||||
return atari.ROWCRS
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
|
||||
@@ -126,9 +125,6 @@ asmsub scroll_down (bool alsocolors @ Pc) clobbers(A) {
|
||||
}
|
||||
|
||||
|
||||
romsub $F2B0 = outchar(ubyte character @ A)
|
||||
romsub $F2Fd = waitkey()
|
||||
|
||||
asmsub chrout(ubyte character @ A) {
|
||||
%asm {{
|
||||
sta _tmp_outchar+1
|
||||
@@ -138,7 +134,7 @@ asmsub chrout(ubyte character @ A) {
|
||||
pha
|
||||
_tmp_outchar
|
||||
lda #0
|
||||
jsr outchar
|
||||
jsr atari.outchar
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
@@ -385,13 +381,19 @@ sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
|
||||
}
|
||||
|
||||
sub plot(ubyte col, ubyte rownum) {
|
||||
column(col)
|
||||
row(rownum)
|
||||
column(col)
|
||||
row(rownum)
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
@(colptr) = get_column()
|
||||
@(rowptr) = get_row()
|
||||
@(colptr) = get_column()
|
||||
@(rowptr) = get_row()
|
||||
}
|
||||
|
||||
asmsub waitkey() -> ubyte @A {
|
||||
%asm {{
|
||||
jmp atari.waitkey
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
|
||||
@@ -391,77 +391,78 @@ asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
rol a
|
||||
sta _use_kernal
|
||||
sei
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #<_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr _irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr _irq_handler_end
|
||||
lda _use_kernal
|
||||
bne +
|
||||
lda #$ff
|
||||
_irq_handler
|
||||
jsr sys.save_prog8_internals
|
||||
cld
|
||||
_modified
|
||||
jsr $ffff ; modified
|
||||
pha
|
||||
jsr sys.restore_prog8_internals
|
||||
pla
|
||||
beq +
|
||||
jmp cbm.IRQDFRT ; continue with normal kernal irq routine
|
||||
+ lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp cbm.IRQDFRT ; continue with normal kernal irq routine
|
||||
|
||||
_use_kernal .byte 0
|
||||
|
||||
_irq_handler_init
|
||||
; save all zp scratch registers as these might be clobbered by the irq routine
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta IRQ_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta IRQ_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta IRQ_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta IRQ_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta IRQ_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta IRQ_SCRATCH_ZPWORD2+1
|
||||
cld
|
||||
rts
|
||||
|
||||
_irq_handler_end
|
||||
; restore all zp scratch registers
|
||||
lda IRQ_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda IRQ_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda IRQ_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda IRQ_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda IRQ_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda IRQ_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
|
||||
IRQ_SCRATCH_ZPB1 .byte 0
|
||||
IRQ_SCRATCH_ZPREG .byte 0
|
||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -481,40 +482,39 @@ asmsub restore_irq() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, bool useKernal @Pc) clobbers(A) {
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
rol a
|
||||
sta set_irq._use_kernal
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sei
|
||||
jsr _setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
sei
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr _setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irq._irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr set_irq._irq_handler_end
|
||||
jsr sys.save_prog8_internals
|
||||
cld
|
||||
_modified
|
||||
jsr $ffff ; modified
|
||||
pha
|
||||
jsr sys.restore_prog8_internals
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda set_irq._use_kernal
|
||||
bne +
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
pla
|
||||
beq +
|
||||
jmp cbm.IRQDFRT ; continue with kernal irq routine
|
||||
+ pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp cbm.IRQDFRT ; continue with kernal irq routine
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
|
||||
@@ -626,7 +626,7 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
asmsub waitkey() -> ubyte @A {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
|
||||
@@ -7,8 +7,9 @@ floats {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
const float π = 3.141592653589793
|
||||
const float PI = π
|
||||
const float TWOPI = 2*π
|
||||
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
@@ -52,7 +53,7 @@ romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, ad
|
||||
|
||||
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
|
||||
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
|
||||
romsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
@@ -77,6 +78,7 @@ romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
||||
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $b8d7 = NORMAL() clobbers(A) ; normalize FAC1
|
||||
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
@@ -175,6 +177,12 @@ asmsub parse_f(str value @AY) -> float @FAC1 {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub normalize(float value @FAC1) -> float @ FAC1 {
|
||||
%asm {{
|
||||
jmp floats.NORMAL
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
@@ -393,78 +393,77 @@ asmsub enable_runstop_and_charsetswitch() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
rol a
|
||||
sta _use_kernal
|
||||
sei
|
||||
sei
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #<_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr _irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr _irq_handler_end
|
||||
lda _use_kernal
|
||||
bne +
|
||||
lda #$ff
|
||||
_irq_handler
|
||||
jsr sys.save_prog8_internals
|
||||
cld
|
||||
_modified
|
||||
jsr $ffff ; modified
|
||||
pha
|
||||
jsr sys.restore_prog8_internals
|
||||
pla
|
||||
beq +
|
||||
jmp cbm.IRQDFRT ; continue with normal kernal irq routine
|
||||
+ lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp cbm.IRQDFRT ; continue with normal kernal irq routine
|
||||
|
||||
_use_kernal .byte 0
|
||||
|
||||
_irq_handler_init
|
||||
; save all zp scratch registers as these might be clobbered by the irq routine
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta IRQ_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta IRQ_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta IRQ_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta IRQ_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta IRQ_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta IRQ_SCRATCH_ZPWORD2+1
|
||||
cld
|
||||
rts
|
||||
|
||||
_irq_handler_end
|
||||
; restore all zp scratch registers
|
||||
lda IRQ_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda IRQ_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda IRQ_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda IRQ_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda IRQ_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda IRQ_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
|
||||
IRQ_SCRATCH_ZPB1 .byte 0
|
||||
IRQ_SCRATCH_ZPREG .byte 0
|
||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
@@ -483,40 +482,39 @@ asmsub restore_irq() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, bool useKernal @Pc) clobbers(A) {
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
rol a
|
||||
sta set_irq._use_kernal
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sei
|
||||
jsr _setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
sei
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr _setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irq._irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr set_irq._irq_handler_end
|
||||
jsr sys.save_prog8_internals
|
||||
cld
|
||||
_modified
|
||||
jsr $ffff ; modified
|
||||
pha
|
||||
jsr sys.restore_prog8_internals
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda set_irq._use_kernal
|
||||
bne +
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
pla
|
||||
pla
|
||||
beq +
|
||||
jmp cbm.IRQDFRT ; continue with kernal irq routine
|
||||
+ pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp cbm.IRQDFRT ; continue with kernal irq routine
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
|
||||
@@ -625,7 +625,7 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
asmsub waitkey() -> ubyte @A {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
; Routines to load and save "BMX" files (commander X16 bitmap format) Version 1.
|
||||
; Only uncompressed images are supported for now.
|
||||
; BMX Specification: https://cx16forum.com/forum/viewtopic.php?t=6945
|
||||
|
||||
%import diskio
|
||||
|
||||
bmx {
|
||||
|
||||
ubyte[32] header
|
||||
str FILEID = petscii:"bmx"
|
||||
|
||||
ubyte bitsperpixel ; consider using set_bpp() when setting this
|
||||
ubyte vera_colordepth ; consider using set_vera_colordepth() when setting this
|
||||
uword width
|
||||
uword height
|
||||
ubyte border
|
||||
uword palette_entries ; 1-256
|
||||
ubyte palette_start
|
||||
ubyte compression
|
||||
uword palette_buffer_ptr = 0 ; should you want to load or save the palette into main memory instead of directly into vram
|
||||
|
||||
uword error_message ; pointer to error message, or 0 if all ok
|
||||
ubyte old_drivenumber
|
||||
|
||||
sub open(ubyte drivenumber, str filename) -> bool {
|
||||
; Open a BMX bitmap file and reads the header information.
|
||||
; Returns true if all is ok, false otherwise + error_message will be set.
|
||||
error_message = 0
|
||||
old_drivenumber = diskio.drivenumber
|
||||
diskio.drivenumber = drivenumber
|
||||
if diskio.f_open(filename) {
|
||||
diskio.reset_read_channel()
|
||||
if read_header() {
|
||||
if parse_header() {
|
||||
if palette_entries>0 {
|
||||
if width<=640 {
|
||||
return true
|
||||
} else
|
||||
error_message = "image too large"
|
||||
} else
|
||||
error_message = "invalid bmx file"
|
||||
} ; note: parse_header sets the error message by itself
|
||||
} else
|
||||
error_message = "invalid bmx file"
|
||||
} else
|
||||
error_message = diskio.status()
|
||||
|
||||
close()
|
||||
return false
|
||||
}
|
||||
|
||||
sub close() {
|
||||
; if you want to close the file before actually loading palette or bitmap data.
|
||||
diskio.f_close()
|
||||
diskio.drivenumber = old_drivenumber
|
||||
}
|
||||
|
||||
sub continue_load(ubyte vbank, uword vaddr) -> bool {
|
||||
; Continues loading the palette and bitmap data from the opened BMX file.
|
||||
; Parameters: the vram bank and address where the bitmap data should go.
|
||||
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
|
||||
; Note: does not change vera screen mode or colordepth! You have to do that yourself!
|
||||
; Returns true if all is ok, false otherwise + error_message will be set.
|
||||
error_message = 0
|
||||
diskio.reset_read_channel()
|
||||
if width==320 or width==640 {
|
||||
if compression==0 {
|
||||
if read_palette() {
|
||||
if not read_bitmap(vbank, vaddr)
|
||||
error_message = "bitmap error"
|
||||
} else
|
||||
error_message = "palette error"
|
||||
} else
|
||||
error_message = "compression not supported"
|
||||
} else
|
||||
error_message = "width not 320 or 640" ; note: use continue_load_stamp() to read other sizes
|
||||
|
||||
close()
|
||||
return error_message==0
|
||||
}
|
||||
|
||||
sub continue_load_stamp(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
|
||||
; Continues loading the palette and bitmap "stamp" data from the opened BMX file.
|
||||
; "Stamp" means: load an image that is smaller than the screen (so we need to pad around it)
|
||||
; Parameters:the vram bank and address where the bitmap data should go,
|
||||
; and the screen width that the stamp image is loaded into.
|
||||
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
|
||||
; Note: does not change vera screen mode or colordepth! You have to do that yourself!
|
||||
; Returns true if all is ok, false otherwise + error_message will be set.
|
||||
error_message = 0
|
||||
diskio.reset_read_channel()
|
||||
if compression==0 {
|
||||
if read_palette() {
|
||||
if not read_bitmap_padded(vbank, vaddr, screenwidth)
|
||||
error_message = "bitmap error"
|
||||
} else
|
||||
error_message = "palette error"
|
||||
} else
|
||||
error_message = "compression not supported"
|
||||
|
||||
close()
|
||||
return error_message==0
|
||||
}
|
||||
|
||||
sub continue_load_only_palette() -> bool {
|
||||
; Continues loading the palette but not the bitmap data from the opened BMX file.
|
||||
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
|
||||
; Returns true if all is ok, false otherwise + error_message will be set.
|
||||
; Afterwards the file is closed and you can no longer read additional data from it!
|
||||
error_message = 0
|
||||
diskio.reset_read_channel()
|
||||
if not read_palette()
|
||||
error_message = "palette error"
|
||||
close()
|
||||
return error_message==0
|
||||
}
|
||||
|
||||
sub save(ubyte drivenumber, str filename, ubyte vbank, uword vaddr, uword screenwidth) -> bool {
|
||||
; Save bitmap and palette data from vram into a BMX file.
|
||||
; First you must have set all bmx.* variables to the correct values! (like width, height..)
|
||||
; Parameters:
|
||||
; drive number and filename to save to,
|
||||
; vram bank and address of the bitmap data to save,
|
||||
; and optionally the screen width if you want to save a "stamp" that's smaller than the screen.
|
||||
; If you're saving the whole screen width, you can leave screenwidth at 0.
|
||||
; Returns: success status. If false, error_message points to the error message string.
|
||||
error_message = 0
|
||||
if compression {
|
||||
error_message = "compression not supported"
|
||||
return false
|
||||
}
|
||||
if screenwidth==0 {
|
||||
if width!=320 and width!=640 {
|
||||
error_message = "width not 320 or 640"
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if width>screenwidth {
|
||||
error_message = "image too large"
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
old_drivenumber = diskio.drivenumber
|
||||
diskio.drivenumber = drivenumber
|
||||
if diskio.f_open_w(filename) {
|
||||
cx16.r0 = diskio.status()
|
||||
if cx16.r0[0]!='0' {
|
||||
error_message = cx16.r0
|
||||
goto save_end
|
||||
}
|
||||
diskio.reset_write_channel()
|
||||
if write_header() {
|
||||
if write_palette() {
|
||||
if not write_bitmap(vbank, vaddr, screenwidth)
|
||||
error_message = "bitmap error"
|
||||
} else
|
||||
error_message = "palette error"
|
||||
} else
|
||||
error_message = "header error"
|
||||
} else
|
||||
error_message = diskio.status()
|
||||
save_end:
|
||||
diskio.f_close_w()
|
||||
diskio.drivenumber = old_drivenumber
|
||||
return error_message==0
|
||||
}
|
||||
|
||||
sub set_bpp(ubyte bpp) {
|
||||
ubyte[8] depths = [0,1,1,2,2,2,2,3]
|
||||
vera_colordepth = depths[bpp-1]
|
||||
}
|
||||
|
||||
sub set_vera_colordepth(ubyte depth) {
|
||||
vera_colordepth = depth
|
||||
bitsperpixel = 1 << depth
|
||||
}
|
||||
|
||||
; ------------------- helper routines, usually not needed to call yourself -------------------------
|
||||
|
||||
sub read_header() -> bool {
|
||||
; load a BMX header from the currently active input file
|
||||
for cx16.r0L in 0 to sizeof(header)-1 {
|
||||
header[cx16.r0L] = cbm.CHRIN()
|
||||
}
|
||||
return not cbm.READST()
|
||||
}
|
||||
|
||||
sub read_palette() -> bool {
|
||||
; load palette data from the currently active input file
|
||||
; if palette_buffer_ptr is not 0, the palette data is read into that memory buffer,
|
||||
; otherwise it is read directly into the palette in vram.
|
||||
cx16.vaddr(1, $fa00+palette_start*2, 0, 1)
|
||||
cx16.r3 = palette_buffer_ptr
|
||||
cx16.r2L = lsb(palette_entries)
|
||||
do {
|
||||
cx16.r4L = cbm.CHRIN()
|
||||
cx16.r4H = cbm.CHRIN()
|
||||
if cx16.r3 {
|
||||
pokew(cx16.r3, cx16.r4) ; into memory
|
||||
cx16.r3+=2
|
||||
} else {
|
||||
cx16.VERA_DATA0 = cx16.r4L ; into vram
|
||||
cx16.VERA_DATA0 = cx16.r4H
|
||||
}
|
||||
cx16.r2L--
|
||||
} until cx16.r2L==0
|
||||
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
|
||||
}
|
||||
|
||||
sub read_bitmap_padded(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
|
||||
; load bitmap "stamp" into vram from the currently active input file
|
||||
cx16.r3 = bytes_per_scanline(width) ; num bytes per image scanline
|
||||
cx16.r2 = bytes_per_scanline(screenwidth) ; num bytes per screen scanline
|
||||
repeat height {
|
||||
cx16.vaddr(vbank, vaddr, 0, 1)
|
||||
read_scanline(cx16.r3)
|
||||
vaddr += cx16.r2
|
||||
if_cs
|
||||
vbank++
|
||||
}
|
||||
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
|
||||
}
|
||||
|
||||
sub read_bitmap(ubyte vbank, uword vaddr) -> bool {
|
||||
; load contiguous bitmap into vram from the currently active input file
|
||||
cx16.r3 = bytes_per_scanline(width)
|
||||
cx16.vaddr(vbank, vaddr, 0, 1)
|
||||
repeat height
|
||||
read_scanline(cx16.r3)
|
||||
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
|
||||
}
|
||||
|
||||
sub read_scanline(uword size) {
|
||||
while size {
|
||||
cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
|
||||
if_cs {
|
||||
; no MACPTR support
|
||||
repeat size
|
||||
cx16.VERA_DATA0 = cbm.CHRIN()
|
||||
return
|
||||
}
|
||||
size -= cx16.r0
|
||||
}
|
||||
}
|
||||
|
||||
sub write_header() -> bool {
|
||||
; save out the BMX header to the currently active output file
|
||||
build_header()
|
||||
for cx16.r0L in 0 to sizeof(header)-1 {
|
||||
cbm.CHROUT(header[cx16.r0L])
|
||||
}
|
||||
return not cbm.READST()
|
||||
}
|
||||
|
||||
sub write_palette() -> bool {
|
||||
; write palette data to the currently active input file
|
||||
; if palette_buffer_ptr is not 0, the palette data is read from that memory buffer,
|
||||
; otherwise it is read directly from the palette in vram.
|
||||
cx16.r3 = palette_buffer_ptr
|
||||
cx16.r2L = lsb(palette_entries)
|
||||
cx16.vaddr(1, $fa00+palette_start*2, 0, 1)
|
||||
do {
|
||||
if cx16.r3 {
|
||||
cbm.CHROUT(@(cx16.r3)) ; from memory
|
||||
cx16.r3++
|
||||
cbm.CHROUT(@(cx16.r3))
|
||||
cx16.r3++
|
||||
} else {
|
||||
cbm.CHROUT(cx16.VERA_DATA0) ; from vram
|
||||
cbm.CHROUT(cx16.VERA_DATA0)
|
||||
}
|
||||
cx16.r2L--
|
||||
} until cx16.r2L==0
|
||||
return not cbm.READST()
|
||||
}
|
||||
|
||||
sub write_bitmap(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
|
||||
; screenwidth=0: save contiguous bitmap from vram to the currently active output file
|
||||
; screenwidth>0: save "stamp" bitmap from vram to the currently active output file
|
||||
cx16.vaddr(vbank, vaddr, 0, 1)
|
||||
cx16.r3 = bytes_per_scanline(width) ; num bytes per image scanline
|
||||
cx16.r2 = 0
|
||||
if screenwidth
|
||||
cx16.r2 = bytes_per_scanline(screenwidth-width) ; num bytes padding per screen scanline
|
||||
repeat height {
|
||||
write_scanline(cx16.r3)
|
||||
repeat cx16.r2
|
||||
cx16.r0L = cx16.VERA_DATA0
|
||||
}
|
||||
return not cbm.READST()
|
||||
|
||||
sub write_scanline(uword size) {
|
||||
while size {
|
||||
cx16.r0L = lsb(size)
|
||||
if msb(size)
|
||||
cx16.r0L = 0 ; 256 bytes
|
||||
cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
|
||||
if_cs {
|
||||
; no MCIOUT support
|
||||
repeat size
|
||||
cbm.CHROUT(cx16.VERA_DATA0)
|
||||
return
|
||||
}
|
||||
size -= cx16.r0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub bytes_per_scanline(uword w) -> uword {
|
||||
ubyte[4] shifts = [3,2,1,0]
|
||||
return w >> shifts[vera_colordepth]
|
||||
}
|
||||
|
||||
sub parse_header() -> bool {
|
||||
if header[0]==FILEID[0] and header[1]==FILEID[1] and header[2]==FILEID[2] {
|
||||
if header[3]==1 { ; only version 1 supported for now
|
||||
bitsperpixel = header[4]
|
||||
vera_colordepth = header[5]
|
||||
width = peekw(&header+6)
|
||||
height = peekw(&header+8)
|
||||
palette_entries = header[10]
|
||||
if palette_entries==0
|
||||
palette_entries=256
|
||||
palette_start = header[11]
|
||||
; the data offset is not needed: data_offset = peekw(&header+12)
|
||||
compression = header[14]
|
||||
border = header[15]
|
||||
return true
|
||||
} else
|
||||
error_message = "unsupported bmx file version"
|
||||
} else
|
||||
error_message = "invalid bmx file"
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
sub build_header() {
|
||||
; build the internal BMX header structure
|
||||
; normally you don't have to call this yourself
|
||||
sys.memset(header, sizeof(header), 0)
|
||||
uword data_offset = palette_entries*$0002
|
||||
data_offset += sizeof(header)
|
||||
header[0] = FILEID[0]
|
||||
header[1] = FILEID[1]
|
||||
header[2] = FILEID[2]
|
||||
header[3] = 1 ; version 1
|
||||
header[4] = bitsperpixel
|
||||
header[5] = vera_colordepth
|
||||
header[6] = lsb(width)
|
||||
header[7] = msb(width)
|
||||
header[8] = lsb(height)
|
||||
header[9] = msb(height)
|
||||
header[10] = lsb(palette_entries)
|
||||
header[11] = palette_start
|
||||
header[12] = lsb(data_offset)
|
||||
header[13] = msb(data_offset)
|
||||
header[14] = compression
|
||||
header[15] = border
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,7 @@ diskio {
|
||||
const ubyte READ_IO_CHANNEL=12
|
||||
const ubyte WRITE_IO_CHANNEL=13
|
||||
|
||||
ubyte drivenumber = 8
|
||||
|
||||
sub set_drive(ubyte number) {
|
||||
drivenumber = number
|
||||
}
|
||||
ubyte drivenumber = 8 ; user programs can set this to the drive number they want to load/save to!
|
||||
|
||||
sub reset_read_channel() {
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
@@ -151,6 +147,7 @@ io_error:
|
||||
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
|
||||
uword buffer_start = filenames_buffer
|
||||
ubyte files_found = 0
|
||||
filenames_buffer[0]=0
|
||||
if lf_start_list(pattern_ptr) {
|
||||
while lf_next_entry() {
|
||||
if list_filetype!="dir" {
|
||||
@@ -285,6 +282,8 @@ close_end:
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
; Returns true if the file is successfully opened and readable.
|
||||
; No need to check status(), unlike f_open_w() !
|
||||
; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
|
||||
; if you're going to read from it yourself instead of using f_read()!
|
||||
f_close()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
|
||||
@@ -24,39 +24,37 @@ emudbg {
|
||||
|
||||
sub is_emulator() -> bool {
|
||||
; Test for emulator presence.
|
||||
; It is recommended to only use the other debug routines if this returns true.
|
||||
; It is recommended to only use the debug registers if this returns true.
|
||||
return EMU_EMU_DETECT1=='1' and EMU_EMU_DETECT2=='6'
|
||||
}
|
||||
|
||||
asmsub console_write(str isoString @R0) clobbers(Y) {
|
||||
sub console_write(str isoString) {
|
||||
; note: make sure the text is in Iso encoding.
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda (cx16.r0),y
|
||||
beq +
|
||||
sta p8_EMU_CPUCLK_U
|
||||
iny
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
if is_emulator() {
|
||||
ubyte chr
|
||||
repeat {
|
||||
chr = @(isoString)
|
||||
if_z
|
||||
break
|
||||
EMU_CPUCLK_U = chr
|
||||
isoString++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asmsub console_chrout(ubyte char @A) {
|
||||
sub console_chrout(ubyte char) {
|
||||
; note: make sure the character is in Iso encoding.
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_U
|
||||
}}
|
||||
if is_emulator()
|
||||
EMU_CPUCLK_U = char
|
||||
}
|
||||
|
||||
asmsub console_value1(ubyte value @A) {
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_M
|
||||
}}
|
||||
sub console_value1(ubyte value) {
|
||||
if is_emulator()
|
||||
EMU_CPUCLK_M = value
|
||||
}
|
||||
|
||||
asmsub console_value2(ubyte value @A) {
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_H
|
||||
}}
|
||||
sub console_value2(ubyte value) {
|
||||
if is_emulator()
|
||||
EMU_CPUCLK_H = value
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,9 @@ floats {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
const float π = 3.141592653589793
|
||||
const float PI = π
|
||||
const float TWOPI = 2*π
|
||||
|
||||
|
||||
; ---- ROM float functions (same as on C128 except base page) ----
|
||||
@@ -28,7 +29,7 @@ romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101
|
||||
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!] see parse_f() instead
|
||||
romsub $fe09 = VAL_1(uword string @XY, ubyte length @A) clobbers(A,X,Y) -> float @FAC1 ; convert ASCII string in XY and length in A, to floating point in FAC1. WARNING: not implemented in the ROM yet!
|
||||
|
||||
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
@@ -71,10 +72,10 @@ romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2
|
||||
; X16 additions
|
||||
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
||||
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
|
||||
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1
|
||||
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (doesn't work, juse use NEGOP() instead!)
|
||||
romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! Have to restore sign manually!
|
||||
romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (s8).A
|
||||
@@ -138,14 +139,38 @@ asmsub FREADUY (ubyte value @Y) {
|
||||
|
||||
asmsub parse_f(str value @AY) -> float @FAC1 {
|
||||
; -- parse a string value of a number to float in FAC1
|
||||
; warning: uses an internal BASIC routine that may be rom version dependent
|
||||
; ($ddf2 is inside the routine for VAL at $ddef)
|
||||
; warning: on older <R47 kernals it uses an internal BASIC routine that is ROM version dependent,
|
||||
; ($deb6 is inside the routine for VAL at $deb3) See basic.sym from x16-rom
|
||||
; TODO once VAL_1 is merged into the kernal properly, remove all the workarounds here
|
||||
%asm {{
|
||||
sta $a9
|
||||
sty $aa
|
||||
ldx VAL_1
|
||||
cpx #$4c ; is there an implementation in VAL_1? (test for JMP)
|
||||
bne + ; no, do it ourselves
|
||||
pha ; yes, count the length and call rom VAL_1.
|
||||
phy
|
||||
jsr prog8_lib.strlen
|
||||
tya
|
||||
jmp $ddf2
|
||||
ply
|
||||
plx
|
||||
jmp VAL_1
|
||||
+ sta $a9 ; 'index' variable
|
||||
sty $aa
|
||||
jsr prog8_lib.strlen
|
||||
lda $deb6
|
||||
cmp #$d0 ; sanity check for kernal routine correct
|
||||
bne +
|
||||
tya
|
||||
jmp $deb6 ; kernal version dependent...
|
||||
+ ; print error message if routine is borked in kernal, and exit program
|
||||
ldy #0
|
||||
- lda _msg,y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ jmp sys.exit
|
||||
|
||||
_msg .text 13,"?val kaputt",13,0
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -159,6 +184,12 @@ sub rndf() -> float {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub normalize(float value @FAC1) -> float @ FAC1 {
|
||||
%asm {{
|
||||
jmp floats.NORMAL
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
|
||||
@@ -936,6 +936,8 @@ skip:
|
||||
}
|
||||
|
||||
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
|
||||
; using verafx 32 bits writes here would make this faster but it's safer to
|
||||
; use verafx only explicitly when you know what you're doing.
|
||||
%asm {{
|
||||
ldy #80
|
||||
- sta cx16.VERA_DATA0
|
||||
|
||||
@@ -890,6 +890,8 @@ skip:
|
||||
}
|
||||
|
||||
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
|
||||
; using verafx 32 bits writes here would make this faster but it's safer to
|
||||
; use verafx only explicitly when you know what you're doing.
|
||||
%asm {{
|
||||
cmp #0
|
||||
beq +
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
; Manipulate the Commander X16's display color palette.
|
||||
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
|
||||
; Should you want to restore the full default palette, you can call cbm.CINT()
|
||||
; The first 16 colors can be restored to their default with set_default16()
|
||||
|
||||
palette {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
uword vera_palette_ptr
|
||||
ubyte cc
|
||||
|
||||
sub set_color(ubyte index, uword color) {
|
||||
vera_palette_ptr = $fa00+(index as uword * 2)
|
||||
@@ -42,19 +42,30 @@ palette {
|
||||
ubyte red
|
||||
ubyte greenblue
|
||||
repeat num_colors {
|
||||
red = @(palette_bytes_ptr) >> 4
|
||||
palette_bytes_ptr++
|
||||
greenblue = @(palette_bytes_ptr) & %11110000
|
||||
palette_bytes_ptr++
|
||||
greenblue |= @(palette_bytes_ptr) >> 4 ; add Blue
|
||||
palette_bytes_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, greenblue)
|
||||
cx16.r1 = color8to4(palette_bytes_ptr)
|
||||
palette_bytes_ptr+=3
|
||||
cx16.vpoke(1, vera_palette_ptr, cx16.r1H) ; $GB
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, red)
|
||||
cx16.vpoke(1, vera_palette_ptr, cx16.r1L) ; $0R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
|
||||
sub color8to4(uword colorpointer) -> uword {
|
||||
; accurately convert 24 bits (3 bytes) RGB color, in that order in memory, to 16 bits $GB;$0R colorvalue
|
||||
cx16.r1 = colorpointer
|
||||
cx16.r0 = channel8to4(@(cx16.r1)) ; (red) -> $00:0R
|
||||
cx16.r0H = channel8to4(@(cx16.r1+1))<<4 ; (green) -> $G0:0R
|
||||
cx16.r0H |= channel8to4(@(cx16.r1+2)) ; (blue) -> $GB:0R
|
||||
return cx16.r0
|
||||
}
|
||||
|
||||
sub channel8to4(ubyte channelvalue) -> ubyte {
|
||||
; accurately convert a single 8 bit color channel value to 4 bits, see https://threadlocalmutex.com/?p=48
|
||||
return msb(channelvalue * $000f + 135)
|
||||
}
|
||||
|
||||
|
||||
sub set_monochrome(uword screencolorRGB, uword drawcolorRGB) {
|
||||
vera_palette_ptr = $fa00
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(screencolorRGB)) ; G,B
|
||||
@@ -78,105 +89,84 @@ palette {
|
||||
}
|
||||
|
||||
sub set_grayscale() {
|
||||
; set first 16 colors to a grayscale gradient from black to white
|
||||
vera_palette_ptr = $fa00
|
||||
cc=0
|
||||
cx16.r2L=0
|
||||
repeat 16 {
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
cx16.vpoke(1, vera_palette_ptr, cx16.r2L)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
cx16.vpoke(1, vera_palette_ptr, cx16.r2L)
|
||||
vera_palette_ptr++
|
||||
cc += $11
|
||||
cx16.r2L += $11
|
||||
}
|
||||
}
|
||||
|
||||
uword[] C64_colorpalette_dark = [ ; this is a darker palette with more contrast
|
||||
$000, ; 0 = black
|
||||
$FFF, ; 1 = white
|
||||
$632, ; 2 = red
|
||||
$7AB, ; 3 = cyan
|
||||
$638, ; 4 = purple
|
||||
$584, ; 5 = green
|
||||
$327, ; 6 = blue
|
||||
$BC6, ; 7 = yellow
|
||||
$642, ; 8 = orange
|
||||
$430, ; 9 = brown
|
||||
$965, ; 10 = light red
|
||||
$444, ; 11 = dark grey
|
||||
$666, ; 12 = medium grey
|
||||
$9D8, ; 13 = light green
|
||||
$65B, ; 14 = light blue
|
||||
$999 ; 15 = light grey
|
||||
]
|
||||
|
||||
uword[] C64_colorpalette_pepto = [ ; # this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||
$000, ; 0 = black
|
||||
$FFF, ; 1 = white
|
||||
$833, ; 2 = red
|
||||
$7cc, ; 3 = cyan
|
||||
$839, ; 4 = purple
|
||||
$5a4, ; 5 = green
|
||||
$229, ; 6 = blue
|
||||
$ef7, ; 7 = yellow
|
||||
$852, ; 8 = orange
|
||||
$530, ; 9 = brown
|
||||
$c67, ; 10 = light red
|
||||
$444, ; 11 = dark grey
|
||||
$777, ; 12 = medium grey
|
||||
$af9, ; 13 = light green
|
||||
$76e, ; 14 = light blue
|
||||
$bbb ; 15 = light grey
|
||||
]
|
||||
|
||||
uword[] C64_colorpalette_light = [ ; this is a lighter palette
|
||||
$000, ; 0 = black
|
||||
$FFF, ; 1 = white
|
||||
$944, ; 2 = red
|
||||
$7CC, ; 3 = cyan
|
||||
$95A, ; 4 = purple
|
||||
$6A5, ; 5 = green
|
||||
$549, ; 6 = blue
|
||||
$CD8, ; 7 = yellow
|
||||
$963, ; 8 = orange
|
||||
$650, ; 9 = brown
|
||||
$C77, ; 10 = light red
|
||||
$666, ; 11 = dark grey
|
||||
$888, ; 12 = medium grey
|
||||
$AE9, ; 13 = light green
|
||||
$87C, ; 14 = light blue
|
||||
$AAA ; 15 = light grey
|
||||
]
|
||||
|
||||
sub set_c64pepto() {
|
||||
vera_palette_ptr = $fa00
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_pepto[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
; set first 16 colors to the "Pepto" PAL commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||
uword[] colors = [
|
||||
$000, ; 0 = black
|
||||
$FFF, ; 1 = white
|
||||
$833, ; 2 = red
|
||||
$7cc, ; 3 = cyan
|
||||
$839, ; 4 = purple
|
||||
$5a4, ; 5 = green
|
||||
$229, ; 6 = blue
|
||||
$ef7, ; 7 = yellow
|
||||
$852, ; 8 = orange
|
||||
$530, ; 9 = brown
|
||||
$c67, ; 10 = light red
|
||||
$444, ; 11 = dark grey
|
||||
$777, ; 12 = medium grey
|
||||
$af9, ; 13 = light green
|
||||
$76e, ; 14 = light blue
|
||||
$bbb ; 15 = light grey
|
||||
]
|
||||
set_rgb(colors, len(colors))
|
||||
}
|
||||
|
||||
sub set_c64light() {
|
||||
vera_palette_ptr = $fa00
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_light[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
sub set_c64ntsc() {
|
||||
; set first 16 colors to a NTSC commodore-64 palette
|
||||
uword[] colors = [
|
||||
$000, ; 0 = black
|
||||
$FFF, ; 1 = white
|
||||
$934, ; 2 = red
|
||||
$9ff, ; 3 = cyan
|
||||
$73f, ; 4 = purple
|
||||
$4b1, ; 5 = green
|
||||
$20c, ; 6 = blue
|
||||
$ee6, ; 7 = yellow
|
||||
$b53, ; 8 = orange
|
||||
$830, ; 9 = brown
|
||||
$f8a, ; 10 = light red
|
||||
$444, ; 11 = dark grey
|
||||
$999, ; 12 = medium grey
|
||||
$9f9, ; 13 = light green
|
||||
$36f, ; 14 = light blue
|
||||
$ccc ; 15 = light grey
|
||||
]
|
||||
set_rgb(colors, len(colors))
|
||||
}
|
||||
|
||||
sub set_c64dark() {
|
||||
vera_palette_ptr = $fa00
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_dark[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
sub set_default16() {
|
||||
; set first 16 colors to the defaults on the X16
|
||||
uword[] colors = [
|
||||
$000, ; 0 = black
|
||||
$fff, ; 1 = white
|
||||
$800, ; 2 = red
|
||||
$afe, ; 3 = cyan
|
||||
$c4c, ; 4 = purple
|
||||
$0c5, ; 5 = green
|
||||
$00a, ; 6 = blue
|
||||
$ee7, ; 7 = yellow
|
||||
$d85, ; 8 = orange
|
||||
$640, ; 9 = brown
|
||||
$f77, ; 10 = light red
|
||||
$333, ; 11 = dark grey
|
||||
$777, ; 12 = medium grey
|
||||
$af6, ; 13 = light green
|
||||
$08f, ; 14 = light blue
|
||||
$bbb ; 15 = light grey
|
||||
]
|
||||
set_rgb(colors, len(colors))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -104,11 +104,11 @@ psg {
|
||||
}
|
||||
}
|
||||
|
||||
sub envelopes_irq() {
|
||||
sub envelopes_irq() -> bool {
|
||||
; 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)
|
||||
; Example: cx16.set_vsync_irq_handler(&psg.envelopes_irq)
|
||||
; NOTE: this routine calls save/restore_vera_context() for you, don't nest this or call it yourself!
|
||||
|
||||
; cx16.r0 = the volume word (volume scaled by 256)
|
||||
@@ -170,6 +170,7 @@ psg {
|
||||
pop(cx16.r2L)
|
||||
pop(cx16.r1L)
|
||||
popw(cx16.r0)
|
||||
return true ; run the system IRQ handler afterwards
|
||||
}
|
||||
|
||||
ubyte[16] envelope_states
|
||||
|
||||
@@ -81,6 +81,33 @@ sprites {
|
||||
cx16.vpoke(1, sprite_reg+1, msb(ypos))
|
||||
}
|
||||
|
||||
sub move(ubyte spritenum, word dx, word dy) {
|
||||
; move a sprite based on its current position
|
||||
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
|
||||
cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
|
||||
cx16.r2 = mkword(cx16.vpeek(1, sprite_reg+3), cx16.vpeek(1, sprite_reg+2)) as word + dy
|
||||
cx16.vpoke(1, sprite_reg, cx16.r1L)
|
||||
cx16.vpoke(1, sprite_reg+1, cx16.r1H)
|
||||
cx16.vpoke(1, sprite_reg+2, cx16.r2L)
|
||||
cx16.vpoke(1, sprite_reg+3, cx16.r2H)
|
||||
}
|
||||
|
||||
sub movex(ubyte spritenum, word dx) {
|
||||
; move a sprite horizontally based on its current position
|
||||
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
|
||||
cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
|
||||
cx16.vpoke(1, sprite_reg, cx16.r1L)
|
||||
cx16.vpoke(1, sprite_reg+1, cx16.r1H)
|
||||
}
|
||||
|
||||
sub movey(ubyte spritenum, word dy) {
|
||||
; move a sprite vertically based on its current position
|
||||
sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
|
||||
cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dy
|
||||
cx16.vpoke(1, sprite_reg, cx16.r1L)
|
||||
cx16.vpoke(1, sprite_reg+1, cx16.r1H)
|
||||
}
|
||||
|
||||
sub getx(ubyte spritenum) -> word {
|
||||
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
|
||||
return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
|
||||
|
||||
@@ -21,7 +21,7 @@ romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard, also called kbd_scan
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
@@ -120,8 +120,8 @@ cx16 {
|
||||
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
&uword edkeyvec = $ac03 ; for intercepting BASIN/CHRIN key strokes. See set_basin_handler()
|
||||
&uword edkeybk = $ac05 ; ...the RAM bank of this routine if not in low ram
|
||||
&uword edkeyvec = $ac03 ; (ram bank 0): for intercepting BASIN/CHRIN key strokes. See set_chrin_keyhandler()
|
||||
&ubyte edkeybk = $ac05 ; ...the RAM bank of the handler routine, if not in low ram
|
||||
|
||||
|
||||
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
|
||||
@@ -238,7 +238,8 @@ cx16 {
|
||||
&ubyte VERA_CTRL = VERA_BASE + $0005
|
||||
&ubyte VERA_IEN = VERA_BASE + $0006
|
||||
&ubyte VERA_ISR = VERA_BASE + $0007
|
||||
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
|
||||
&ubyte VERA_IRQLINE_L = VERA_BASE + $0008 ; write only
|
||||
&ubyte VERA_SCANLINE_L = VERA_BASE + $0008 ; read only
|
||||
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009 ; DCSEL= 0
|
||||
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A ; DCSEL= 0
|
||||
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B ; DCSEL= 0
|
||||
@@ -271,14 +272,29 @@ cx16 {
|
||||
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
|
||||
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
|
||||
|
||||
; experimental Vera FX registers: (depends on particular value set in VERA_CTRL!!!)
|
||||
; Vera FX registers: (accessing depends on particular DCSEL value set in VERA_CTRL!)
|
||||
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_MULT = VERA_BASE + $000C
|
||||
&ubyte VERA_FX_TILEBASE = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_MAPBASE = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_MULT = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_X_INCR_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_X_INCR_H = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_Y_INCR_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_Y_INCR_H = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_X_POS_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_X_POS_H = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_Y_POS_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_Y_POS_H = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_X_POS_S = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_Y_POS_S = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_POLY_FILL_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000A
|
||||
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000B
|
||||
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000C
|
||||
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009 ; (DCSEL=6)
|
||||
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_ACCUM = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009
|
||||
|
||||
|
||||
; VERA_PSG_BASE = $1F9C0
|
||||
@@ -399,8 +415,8 @@ romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
romsub $ff44 = MACPTR(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $feb1 = MCIOUT(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $ff47 = enter_basic(bool cold_or_warm @Pc) clobbers(A,X,Y)
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, uword jiffiesweekday @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, uword @R3 ; result registers see clock_set_date_time()
|
||||
|
||||
; keyboard, mouse, joystick
|
||||
; note: also see the kbdbuf_clear() helper routine below!
|
||||
@@ -567,7 +583,6 @@ asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrO
|
||||
|
||||
asmsub vaddr_clone(ubyte port @A) clobbers (A,X,Y) {
|
||||
; -- clones Vera addresses from the given source port to the other one.
|
||||
; leaves CTRL on the destination port.
|
||||
%asm {{
|
||||
sta VERA_CTRL
|
||||
ldx VERA_ADDR_L
|
||||
@@ -580,6 +595,8 @@ asmsub vaddr_clone(ubyte port @A) clobbers (A,X,Y) {
|
||||
sty VERA_ADDR_M
|
||||
ply
|
||||
sty VERA_ADDR_H
|
||||
eor #1
|
||||
sta VERA_CTRL
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -832,14 +849,16 @@ asmsub restore_vera_context() clobbers(A) {
|
||||
|
||||
|
||||
asmsub set_chrin_keyhandler(ubyte handlerbank @A, uword handler @XY) clobbers(A) {
|
||||
; Install a custom CHRIN (BASIN) key handler. Call this before each line you want to read.
|
||||
; Install a custom CHRIN (BASIN) key handler in a safe manner. Call this before each line you want to read.
|
||||
; See https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2002%20-%20Editor.md#custom-basin-petscii-code-override-handler
|
||||
%asm {{
|
||||
sei
|
||||
sta cx16.edkeybk
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda $00
|
||||
pha
|
||||
stz $00
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta cx16.edkeybk
|
||||
stx cx16.edkeyvec
|
||||
sty cx16.edkeyvec+1
|
||||
pla
|
||||
@@ -848,8 +867,199 @@ asmsub restore_vera_context() clobbers(A) {
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_chrin_keyhandler() -> ubyte @R0, uword @R1 {
|
||||
; --- retrieve the currently set CHRIN keyhandler in a safe manner, bank in r0L, handler address in R1.
|
||||
%asm {{
|
||||
sei
|
||||
lda $00
|
||||
pha
|
||||
stz $00
|
||||
lda cx16.edkeybk
|
||||
sta cx16.r0L
|
||||
lda cx16.edkeyvec
|
||||
ldy cx16.edkeyvec+1
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
pla
|
||||
sta $00
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
; Commander X16 IRQ dispatcher routines
|
||||
|
||||
inline asmsub disable_irqs() clobbers(A) {
|
||||
; Disable all Vera IRQ sources. Note that it does NOT set the CPU IRQ disabled status bit!
|
||||
%asm {{
|
||||
lda #%00001111
|
||||
trb cx16.VERA_IEN
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
|
||||
; Install the "master IRQ handler" that will dispatch IRQs
|
||||
; to the registered handler for each type. (Only Vera IRQs supported for now).
|
||||
; The handlers don't need to clear its ISR bit, but have to return 0 or 1 in A,
|
||||
; where 1 means: continue with the system IRQ handler, 0 means: don't call that.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
bcc +
|
||||
lda #%00001111
|
||||
trb cx16.VERA_IEN ; disable all IRQ sources
|
||||
+ lda #<_irq_dispatcher
|
||||
ldy #>_irq_dispatcher
|
||||
sta cx16.CINV
|
||||
sty cx16.CINV+1
|
||||
plp
|
||||
rts
|
||||
|
||||
_irq_dispatcher
|
||||
; order of handling: LINE, SPRCOL, AFLOW, VSYNC.
|
||||
jsr sys.save_prog8_internals
|
||||
cld
|
||||
lda cx16.VERA_ISR
|
||||
and cx16.VERA_IEN ; only consider the bits for sources that can actually raise the IRQ
|
||||
|
||||
bit #2
|
||||
beq +
|
||||
_mod_line_jump
|
||||
jsr _default_line_handler ; modified
|
||||
ldy #2
|
||||
sty cx16.VERA_ISR
|
||||
bra _dispatch_end
|
||||
+
|
||||
bit #4
|
||||
beq +
|
||||
_mod_sprcol_jump
|
||||
jsr _default_sprcol_handler ; modified
|
||||
ldy #4
|
||||
sty cx16.VERA_ISR
|
||||
bra _dispatch_end
|
||||
+
|
||||
bit #8
|
||||
beq +
|
||||
_mod_aflow_jump
|
||||
jsr _default_aflow_handler ; modified
|
||||
; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
|
||||
bra _dispatch_end
|
||||
+
|
||||
bit #1
|
||||
beq +
|
||||
_mod_vsync_jump
|
||||
jsr _default_vsync_handler ; modified
|
||||
cmp #0
|
||||
bne _dispatch_end
|
||||
ldy #1
|
||||
sty cx16.VERA_ISR
|
||||
bra _return_irq
|
||||
+
|
||||
lda #0
|
||||
_dispatch_end
|
||||
cmp #0
|
||||
beq _return_irq
|
||||
jsr sys.restore_prog8_internals
|
||||
jmp (sys.restore_irq._orig_irqvec) ; continue with normal kernal irq routine
|
||||
_return_irq
|
||||
jsr sys.restore_prog8_internals
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
_default_vsync_handler
|
||||
lda #1
|
||||
rts
|
||||
_default_line_handler
|
||||
lda #0
|
||||
rts
|
||||
_default_sprcol_handler
|
||||
lda #0
|
||||
rts
|
||||
_default_aflow_handler
|
||||
lda #0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_vsync_irq_handler(uword address @AY) clobbers(A) {
|
||||
; Sets the VSYNC irq handler to use with enable_irq_handlers(). Also enables VSYNC irqs.
|
||||
; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta enable_irq_handlers._mod_vsync_jump+1
|
||||
sty enable_irq_handlers._mod_vsync_jump+2
|
||||
lda #1
|
||||
tsb cx16.VERA_IEN
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_line_irq_handler(uword rasterline @R0, uword address @AY) clobbers(A,Y) {
|
||||
; Sets the LINE irq handler to use with enable_irq_handlers(), for the given rasterline. Also enables LINE irqs.
|
||||
; You can use sys.set_rasterline() later to adjust the rasterline on which to trigger.
|
||||
; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta enable_irq_handlers._mod_line_jump+1
|
||||
sty enable_irq_handlers._mod_line_jump+2
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr sys.set_rasterline
|
||||
lda #2
|
||||
tsb cx16.VERA_IEN
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_sprcol_irq_handler(uword address @AY) clobbers(A) {
|
||||
; Sets the SPRCOL irq handler to use with enable_irq_handlers(). Also enables SPRCOL irqs.
|
||||
; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta enable_irq_handlers._mod_sprcol_jump+1
|
||||
sty enable_irq_handlers._mod_sprcol_jump+2
|
||||
lda #4
|
||||
tsb cx16.VERA_IEN
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_aflow_irq_handler(uword address @AY) clobbers(A) {
|
||||
; Sets the AFLOW irq handler to use with enable_irq_handlers(). Also enables AFLOW irqs.
|
||||
; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta enable_irq_handlers._mod_aflow_jump+1
|
||||
sty enable_irq_handlers._mod_aflow_jump+2
|
||||
lda #8
|
||||
tsb cx16.VERA_IEN
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
inline asmsub disable_irq_handlers() {
|
||||
; back to the system default IRQ handler.
|
||||
%asm {{
|
||||
jsr sys.restore_irq
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
@@ -929,79 +1139,38 @@ asmsub cleanup_at_exit() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
; Sets the handler for the VSYNC interrupt, and enable that interrupt.
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
rol a
|
||||
sta _use_kernal
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta cx16.CINV
|
||||
lda #>_irq_handler
|
||||
sta cx16.CINV+1
|
||||
lda cx16.VERA_IEN
|
||||
ora #%00000001 ; enable the vsync irq
|
||||
sta cx16.VERA_IEN
|
||||
cli
|
||||
rts
|
||||
sei
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #<_irq_handler
|
||||
sta cx16.CINV
|
||||
lda #>_irq_handler
|
||||
sta cx16.CINV+1
|
||||
lda #1
|
||||
tsb cx16.VERA_IEN ; enable the vsync irq
|
||||
cli
|
||||
rts
|
||||
|
||||
_irq_handler jsr _irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr _irq_handler_end
|
||||
lda _use_kernal
|
||||
bne +
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
lda #1
|
||||
_irq_handler
|
||||
jsr sys.save_prog8_internals
|
||||
cld
|
||||
_modified
|
||||
jsr $ffff ; modified
|
||||
pha
|
||||
jsr sys.restore_prog8_internals
|
||||
pla
|
||||
beq +
|
||||
jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
|
||||
+ lda #1
|
||||
sta cx16.VERA_ISR ; clear Vera Vsync irq status
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
|
||||
|
||||
_use_kernal .byte 0
|
||||
|
||||
_irq_handler_init
|
||||
; save all zp scratch registers as these might be clobbered by the irq routine
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta IRQ_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta IRQ_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta IRQ_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta IRQ_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta IRQ_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta IRQ_SCRATCH_ZPWORD2+1
|
||||
cld
|
||||
rts
|
||||
|
||||
_irq_handler_end
|
||||
; restore all zp scratch registers
|
||||
lda IRQ_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda IRQ_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda IRQ_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda IRQ_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda IRQ_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda IRQ_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
|
||||
IRQ_SCRATCH_ZPB1 .byte 0
|
||||
IRQ_SCRATCH_ZPREG .byte 0
|
||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
@@ -1012,8 +1181,8 @@ asmsub restore_irq() clobbers(A) {
|
||||
lda _orig_irqvec+1
|
||||
sta cx16.CINV+1
|
||||
lda cx16.VERA_IEN
|
||||
and #%11110000 ; disable all Vera IRQs
|
||||
ora #%00000001 ; enable only the vsync Irq
|
||||
and #%11110000 ; disable all Vera IRQs but the vsync
|
||||
ora #%00000001
|
||||
sta cx16.VERA_IEN
|
||||
cli
|
||||
rts
|
||||
@@ -1022,15 +1191,16 @@ _orig_irqvec .word 0
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
; Sets the handler for the LINE interrupt, and enable (only) that interrupt.
|
||||
%asm {{
|
||||
sei
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sei
|
||||
lda cx16.VERA_IEN
|
||||
and #%11110000 ; clear other IRQs
|
||||
ora #%00000010 ; enable the line (raster) irq
|
||||
and #%11110000 ; disable all irqs but the line(raster) one
|
||||
ora #%00000010
|
||||
sta cx16.VERA_IEN
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
@@ -1043,13 +1213,13 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irq._irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr set_irq._irq_handler_end
|
||||
jsr sys.save_prog8_internals
|
||||
cld
|
||||
_modified jsr $ffff ; modified
|
||||
jsr sys.restore_prog8_internals
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
lda cx16.VERA_ISR
|
||||
ora #%00000010
|
||||
sta cx16.VERA_ISR ; clear Vera line irq status
|
||||
lda #2
|
||||
tsb cx16.VERA_ISR ; clear Vera line irq status
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
@@ -1059,16 +1229,19 @@ _modified jsr $ffff ; modified
|
||||
|
||||
asmsub set_rasterline(uword line @AY) {
|
||||
%asm {{
|
||||
sta cx16.VERA_IRQ_LINE_L
|
||||
lda cx16.VERA_IEN
|
||||
and #%01111111
|
||||
sta cx16.VERA_IEN
|
||||
php
|
||||
sei
|
||||
sta cx16.VERA_IRQLINE_L
|
||||
tya
|
||||
lsr a
|
||||
ror a
|
||||
and #%10000000
|
||||
ora cx16.VERA_IEN
|
||||
sta cx16.VERA_IEN
|
||||
bcs +
|
||||
lda #%10000000
|
||||
trb cx16.VERA_IEN
|
||||
plp
|
||||
rts
|
||||
+ lda #%10000000
|
||||
tsb cx16.VERA_IEN
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -1282,7 +1455,47 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
jsr cbm.CLRCHN ; reset i/o channels
|
||||
|
||||
@@ -796,7 +796,7 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
asmsub waitkey() -> ubyte @A {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
|
||||
@@ -39,19 +39,19 @@ verafx {
|
||||
|
||||
if (amountof32bits & %1111110000000011) == 0 {
|
||||
repeat lsb(amountof32bits >> 2)
|
||||
unroll 4 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
|
||||
unroll 4 cx16.VERA_DATA0=0 ; write 4*4 bytes at a time, unrolled
|
||||
}
|
||||
else if (amountof32bits & %1111111000000001) == 0 {
|
||||
repeat lsb(amountof32bits >> 1)
|
||||
unroll 2 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
|
||||
unroll 2 cx16.VERA_DATA0=0 ; write 2*4 bytes at a time, unrolled
|
||||
}
|
||||
else if (lsb(amountof32bits) & 3) == 0 {
|
||||
repeat amountof32bits >> 2
|
||||
unroll 4 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
|
||||
unroll 4 cx16.VERA_DATA0=0 ; write 4*4 bytes at a time, unrolled
|
||||
}
|
||||
else if (lsb(amountof32bits) & 1) == 0 {
|
||||
repeat amountof32bits >> 1
|
||||
unroll 2 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
|
||||
unroll 2 cx16.VERA_DATA0=0 ; write 2*4 bytes at a time, unrolled
|
||||
}
|
||||
else {
|
||||
repeat amountof32bits
|
||||
|
||||
@@ -10,11 +10,7 @@ diskio {
|
||||
const ubyte READ_IO_CHANNEL=12
|
||||
const ubyte WRITE_IO_CHANNEL=13
|
||||
|
||||
ubyte drivenumber = 8
|
||||
|
||||
sub set_drive(ubyte number) {
|
||||
drivenumber = number
|
||||
}
|
||||
ubyte drivenumber = 8 ; user programs can set this to the drive number they want to load/save to!
|
||||
|
||||
sub reset_read_channel() {
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
@@ -275,6 +271,10 @@ close_end:
|
||||
sub f_open(uword filenameptr) -> bool {
|
||||
; -- open a file for iterative reading with f_read
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
; Returns true if the file is successfully opened and readable.
|
||||
; No need to check status(), unlike f_open_w() !
|
||||
; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
|
||||
; if you're going to read from it yourself instead of using f_read()!
|
||||
f_close()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
@@ -390,6 +390,11 @@ _end rts
|
||||
|
||||
sub f_open_w(uword filenameptr) -> bool {
|
||||
; -- open a file for iterative writing with f_write
|
||||
; WARNING: returns true if the open command was received by the device,
|
||||
; but this can still mean the file wasn't successfully opened for writing!
|
||||
; (for example, if it already exists). This is different than f_open()!
|
||||
; To be 100% sure if this call was successful, you have to use status()
|
||||
; and check the drive's status message!
|
||||
f_close_w()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
|
||||
@@ -2,15 +2,10 @@ floats {
|
||||
; the floating point functions shared across compiler targets
|
||||
%option merge, no_symbol_prefixing
|
||||
|
||||
sub print_f(float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
asmsub print_f(float value @FAC1) clobbers(A,X,Y) {
|
||||
; ---- prints the floating point value (without a newline). No leading space (unlike BASIC)!
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
jsr str_f
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
@@ -21,6 +16,25 @@ sub print_f(float value) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_f(float value @FAC1) clobbers(X) -> str @AY {
|
||||
; ---- converts the floating point value to a string. No leading space!
|
||||
%asm {{
|
||||
jsr FOUT
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp #' '
|
||||
bne +
|
||||
inc P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub pow(float value, float power) -> float {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_W1
|
||||
|
||||
@@ -305,6 +305,46 @@ _longcopy
|
||||
; PET doesn't have a key to swap case, so no-op
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta save_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta save_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta save_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta save_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta save_SCRATCH_ZPWORD2+1
|
||||
rts
|
||||
save_SCRATCH_ZPB1 .byte 0
|
||||
save_SCRATCH_ZPREG .byte 0
|
||||
save_SCRATCH_ZPWORD1 .word 0
|
||||
save_SCRATCH_ZPWORD2 .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_prog8_internals() {
|
||||
%asm {{
|
||||
lda save_prog8_internals.save_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
|
||||
@@ -495,7 +495,7 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
asmsub waitkey() -> ubyte @A {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
|
||||
@@ -441,7 +441,7 @@ _return_minusone
|
||||
|
||||
|
||||
strlen .proc
|
||||
; -- returns the number of bytes in the string in AY, in Y.
|
||||
; -- returns the number of bytes in the string in AY, in Y. Clobbers A.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
|
||||
@@ -361,4 +361,83 @@ fail clc ; yes, no match found, return with c=0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub hash(str string @R0) -> ubyte @A {
|
||||
; experimental 8 bit hashing function.
|
||||
; hash(-1)=179; clear carry; hash(i) = ROL hash(i-1) XOR string[i]
|
||||
; On the English word list in /usr/share/dict/words it seems to have a pretty even distribution
|
||||
%asm {{
|
||||
lda #179
|
||||
sta P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
clc
|
||||
- lda (cx16.r0),y
|
||||
beq +
|
||||
rol P8ZP_SCRATCH_REG
|
||||
eor P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
iny
|
||||
bne -
|
||||
+ lda P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub isdigit(ubyte character @A) -> bool @Pc {
|
||||
%asm {{
|
||||
cmp #'0'
|
||||
bcs +
|
||||
rts
|
||||
+ cmp #'9'+1
|
||||
bcc +
|
||||
clc
|
||||
rts
|
||||
+ sec
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub isupper(ubyte character @A) -> bool @Pc {
|
||||
; shifted petscii has 2 ranges that contain the upper case letters...
|
||||
%asm {{
|
||||
cmp #97
|
||||
bcs +
|
||||
rts
|
||||
+ cmp #122+1
|
||||
bcc _yes
|
||||
cmp #193
|
||||
bcs +
|
||||
rts
|
||||
+ cmp #218+1
|
||||
bcc _yes
|
||||
clc
|
||||
rts
|
||||
_yes sec
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub islower(ubyte character @A) -> bool @Pc {
|
||||
%asm {{
|
||||
cmp #'a'
|
||||
bcs +
|
||||
rts
|
||||
+ cmp #'z'+1
|
||||
bcc +
|
||||
clc
|
||||
rts
|
||||
+ sec
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub isletter(ubyte character @A) -> bool @Pc {
|
||||
%asm {{
|
||||
jsr islower
|
||||
bcs +
|
||||
jmp isupper
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
; Emulator debug interface. (reflecting the Commander X16 emudbg library module)
|
||||
; Docs: https://github.com/X16Community/x16-emulator#emulator-io-registers
|
||||
|
||||
%import textio
|
||||
|
||||
emudbg {
|
||||
|
||||
sub is_emulator() -> bool {
|
||||
; Test for emulator presence.
|
||||
return true ; VM is always 'emulator'
|
||||
}
|
||||
|
||||
sub console_write(str message) {
|
||||
txt.print("[EMUDBG: ")
|
||||
txt.print(message)
|
||||
txt.chrout(']')
|
||||
}
|
||||
|
||||
sub console_chrout(ubyte char) {
|
||||
txt.chrout(char)
|
||||
}
|
||||
|
||||
sub console_value1(ubyte value) {
|
||||
txt.print("[EMUDBG debug 1: ")
|
||||
txt.print_uwhex(value, true)
|
||||
txt.print("]\n")
|
||||
}
|
||||
|
||||
sub console_value2(ubyte value) {
|
||||
txt.print("[EMUDBG debug 2: ")
|
||||
txt.print_uwhex(value, true)
|
||||
txt.print("]\n")
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
floats {
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
const float π = 3.141592653589793
|
||||
const float PI = π
|
||||
const float TWOPI = 2*π
|
||||
|
||||
|
||||
sub print_f(float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
; ---- prints the floating point value (without a newline and no leading spaces).
|
||||
%ir {{
|
||||
loadm.f fr65535,floats.print_f.value
|
||||
syscall 25 (fr65535.f)
|
||||
@@ -16,6 +18,18 @@ sub print_f(float value) {
|
||||
}}
|
||||
}
|
||||
|
||||
sub str_f(float value) -> str {
|
||||
; ---- converts the floating point value to a string (no leading spaces)
|
||||
str @shared buffer=" "*20
|
||||
%ir {{
|
||||
load.w r65535,floats.str_f.buffer
|
||||
loadm.f fr65535,floats.str_f.value
|
||||
syscall 47 (r65535.w, fr65535.f)
|
||||
load.w r0,floats.str_f.buffer
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub parse_f(str value) -> float {
|
||||
; -- parse a string value of a number to float
|
||||
%ir {{
|
||||
@@ -155,4 +169,8 @@ sub clampf(float value, float minimum, float maximum) -> float {
|
||||
return maximum
|
||||
}
|
||||
|
||||
sub normalize(float value) -> float {
|
||||
return value
|
||||
}
|
||||
|
||||
}
|
||||
@@ -157,4 +157,37 @@ string {
|
||||
return false
|
||||
return compare(st + str_len - suffix_len, suffix) == 0
|
||||
}
|
||||
}
|
||||
|
||||
sub hash(str st) -> ubyte {
|
||||
; experimental 8 bit hashing function.
|
||||
; hash(-1)=179; hash(i) = ROL hash(i-1) XOR string[i]
|
||||
; (experimental because the quality of the resulting hash value still has to be determined)
|
||||
ubyte hashcode = 179
|
||||
ubyte ix
|
||||
sys.clear_carry()
|
||||
repeat {
|
||||
if st[ix] {
|
||||
rol(hashcode)
|
||||
hashcode ^= st[ix]
|
||||
ix++
|
||||
} else
|
||||
return hashcode
|
||||
}
|
||||
}
|
||||
|
||||
sub isdigit(ubyte character) -> bool {
|
||||
return character>='0' and character<='9'
|
||||
}
|
||||
|
||||
sub isupper(ubyte character) -> bool {
|
||||
return character>='A' and character<='Z'
|
||||
}
|
||||
|
||||
sub islower(ubyte character) -> bool {
|
||||
return character>='a' and character<='z'
|
||||
}
|
||||
|
||||
sub isletter(ubyte character) -> bool {
|
||||
return islower(character) or isupper(character)
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,14 @@ sys {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub save_prog8_internals() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub restore_prog8_internals() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub gfx_enable(ubyte mode) {
|
||||
%ir {{
|
||||
loadm.b r65535,sys.gfx_enable.mode
|
||||
|
||||
@@ -5,11 +5,11 @@ package prog8.buildversion
|
||||
*/
|
||||
const val MAVEN_GROUP = "prog8"
|
||||
const val MAVEN_NAME = "compiler"
|
||||
const val VERSION = "9.6-SNAPSHOT"
|
||||
const val GIT_REVISION = 4159
|
||||
const val GIT_SHA = "335213b55f971452a05c904216cb0fd72f5518a1"
|
||||
const val GIT_DATE = "2023-10-21T00:16:58Z"
|
||||
const val VERSION = "9.7"
|
||||
const val GIT_REVISION = 4286
|
||||
const val GIT_SHA = "cfc9c15f1e9fe96940e170fb14ad3a957109977c"
|
||||
const val GIT_DATE = "2023-12-10T15:22:00Z"
|
||||
const val GIT_BRANCH = "master"
|
||||
const val BUILD_DATE = "2023-10-21T20:22:14Z"
|
||||
const val BUILD_UNIX_TIME = 1697919734197L
|
||||
const val BUILD_DATE = "2023-12-10T15:44:19Z"
|
||||
const val BUILD_UNIX_TIME = 1702223059842L
|
||||
const val DIRTY = 1
|
||||
@@ -105,20 +105,23 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
|
||||
}
|
||||
|
||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
// 1 arg, type = anything, result type = ubyte
|
||||
// 1 arg, type = anything, result type = ubyte or uword
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("sizeof requires one argument", position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("sizeof argument should be an identifier", position)
|
||||
if(args[0] !is IdentifierReference && args[0] !is NumericLiteral)
|
||||
throw SyntaxError("sizeof argument should be an identifier or number", position)
|
||||
|
||||
val dt = args[0].inferType(program)
|
||||
if(dt.isKnown) {
|
||||
if(args[0] is NumericLiteral)
|
||||
return NumericLiteral.optimalInteger(program.memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position)
|
||||
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program)
|
||||
?: throw CannotEvaluateException("sizeof", "no target")
|
||||
|
||||
return when {
|
||||
dt.isArray -> {
|
||||
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||
val length = (target as VarDecl).arraysize?.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
|
||||
NumericLiteral.optimalInteger(program.memsizer.memorySize(elementDt) * length, position)
|
||||
}
|
||||
|
||||
@@ -110,14 +110,18 @@ class ModuleImporter(private val program: Program,
|
||||
// their content has to be merged into already existing other block with the same name.
|
||||
val blocks = importedModule.statements.filterIsInstance<Block>()
|
||||
for(block in blocks) {
|
||||
if("merge" in block.options()) {
|
||||
val existingBlock = program.allBlocks.firstOrNull { it.name==block.name && it !== block}
|
||||
if(existingBlock!=null) {
|
||||
val blockHasMergeOption = "merge" in block.options()
|
||||
val existingBlock = program.allBlocks.firstOrNull { it.name==block.name && it !== block}
|
||||
if(existingBlock!=null) {
|
||||
val existingBlockHasMergeOption = "merge" in existingBlock.options()
|
||||
if (blockHasMergeOption || existingBlockHasMergeOption) {
|
||||
// transplant the contents
|
||||
existingBlock.statements.addAll(block.statements.filter { it !is Directive })
|
||||
importedModule.statements.remove(block)
|
||||
} else {
|
||||
val merges = block.statements.filter { it is Directive && it.directive=="%option" && it.args.any { a->a.name=="merge" } }
|
||||
block.statements.removeAll(merges.toSet())
|
||||
|
||||
if(blockHasMergeOption && !existingBlockHasMergeOption) {
|
||||
existingBlock.statements.add(0, Directive("%option", listOf(DirectiveArg(null, "merge", null, block.position)), block.position))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.compiler.builtinFunctionReturnType
|
||||
@@ -56,13 +55,14 @@ internal class AstChecker(private val program: Program,
|
||||
val directives = module.statements.filterIsInstance<Directive>().groupBy { it.directive }
|
||||
directives.filter { it.value.size > 1 }.forEach{ entry ->
|
||||
when(entry.key) {
|
||||
"%output", "%launcher", "%zeropage", "%address" ->
|
||||
"%output", "%launcher", "%zeropage", "%address", "%encoding" ->
|
||||
entry.value.forEach { errors.err("directive can just occur once", it.position) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
checkLongType(identifier)
|
||||
val stmt = identifier.targetStatement(program)
|
||||
if(stmt==null)
|
||||
errors.undefined(identifier.nameInSource, identifier.position)
|
||||
@@ -280,6 +280,10 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
override fun visit(numLiteral: NumericLiteral) {
|
||||
checkLongType(numLiteral)
|
||||
}
|
||||
|
||||
private fun hasReturnOrJumpOrRts(scope: IStatementContainer): Boolean {
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
@@ -314,7 +318,7 @@ internal class AstChecker(private val program: Program,
|
||||
err("cannot redefine a built-in function")
|
||||
|
||||
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine && !subroutine.definingBlock.isInLibrary)
|
||||
errors.warn("subroutine has a large number of parameters, this slows down code execution a lot", subroutine.position)
|
||||
errors.warn("subroutine has a large number of parameters, this is slow if called often", subroutine.position)
|
||||
|
||||
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
|
||||
if(uniqueNames.size!=subroutine.parameters.size)
|
||||
@@ -529,6 +533,9 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(assignTarget: AssignTarget) {
|
||||
if(assignTarget.inferType(program).istype(DataType.LONG))
|
||||
errors.err("integer overflow", assignTarget.position)
|
||||
|
||||
super.visit(assignTarget)
|
||||
|
||||
val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
|
||||
@@ -578,6 +585,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(addressOf: AddressOf) {
|
||||
checkLongType(addressOf)
|
||||
val variable=addressOf.identifier.targetVarDecl(program)
|
||||
if(variable!=null && variable.type==VarDeclType.CONST)
|
||||
errors.err("invalid pointer-of operand type", addressOf.position)
|
||||
@@ -585,6 +593,12 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
if(decl.names.size>1)
|
||||
throw InternalCompilerException("vardecls with multiple names should have been converted into individual vardecls")
|
||||
|
||||
if(decl.datatype==DataType.LONG)
|
||||
errors.err("integer overflow", decl.position)
|
||||
|
||||
fun err(msg: String) = errors.err(msg, decl.position)
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
@@ -594,7 +608,7 @@ internal class AstChecker(private val program: Program,
|
||||
// CONST can only occur on simple types (byte, word, float)
|
||||
if(decl.type== VarDeclType.CONST) {
|
||||
if (decl.datatype !in NumericDatatypes)
|
||||
err("const modifier can only be used on numeric types (byte, word, float)")
|
||||
err("const can only be used on numeric types (byte, word, float)")
|
||||
}
|
||||
|
||||
// FLOATS enabled?
|
||||
@@ -832,9 +846,20 @@ internal class AstChecker(private val program: Program,
|
||||
if(directive.args.any {it.name !in arrayOf("align_word", "align_page", "no_symbol_prefixing", "force_output", "merge", "splitarrays", "verafxmuls")})
|
||||
err("using an option that is not valid for blocks")
|
||||
}
|
||||
if(directive.parent is Module) {
|
||||
if(directive.args.any {it.name !in arrayOf("enable_floats", "no_sysinit", "splitarrays")})
|
||||
err("using an option that is not valid for modules")
|
||||
}
|
||||
if(directive.args.any { it.name=="verafxmuls" } && compilerOptions.compTarget.name != Cx16Target.NAME)
|
||||
err("verafx option is only valid on cx16 target")
|
||||
}
|
||||
"%encoding" -> {
|
||||
if(directive.parent !is Module)
|
||||
err("this directive may only occur at module level")
|
||||
val allowedEncodings = Encoding.entries.map {it.prefix}
|
||||
if(directive.args.size!=1 || directive.args[0].name !in allowedEncodings)
|
||||
err("invalid encoding directive, expected one of ${allowedEncodings}")
|
||||
}
|
||||
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
|
||||
}
|
||||
super.visit(directive)
|
||||
@@ -912,6 +937,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(expr: PrefixExpression) {
|
||||
checkLongType(expr)
|
||||
val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(dt==DataType.UNDEFINED)
|
||||
return // any error should be reported elsewhere
|
||||
@@ -932,6 +958,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(expr: BinaryExpression) {
|
||||
super.visit(expr)
|
||||
checkLongType(expr)
|
||||
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
@@ -950,7 +977,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if(expr.operator=="+" || expr.operator=="-") {
|
||||
if(leftDt == DataType.STR || rightDt == DataType.STR || leftDt in ArrayDatatypes || rightDt in ArrayDatatypes) {
|
||||
errors.err("missing & (address-of) on the string operand", expr.position)
|
||||
errors.err("missing & (address-of) on the operand", expr.position)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1033,6 +1060,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(typecast: TypecastExpression) {
|
||||
checkLongType(typecast)
|
||||
if(typecast.type in IterableDatatypes)
|
||||
errors.err("cannot type cast to string or array type", typecast.position)
|
||||
|
||||
@@ -1079,6 +1107,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
checkLongType(functionCallExpr)
|
||||
// this function call is (part of) an expression, which should be in a statement somewhere.
|
||||
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
|
||||
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
|
||||
@@ -1134,6 +1163,11 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(functionCallExpr)
|
||||
}
|
||||
|
||||
override fun visit(bfc: BuiltinFunctionCall) {
|
||||
checkLongType(bfc)
|
||||
super.visit(bfc)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors)
|
||||
if(targetStatement!=null) {
|
||||
@@ -1213,6 +1247,13 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
private fun checkFunctionCall(target: Statement, args: List<Expression>, position: Position) {
|
||||
if(target is BuiltinFunctionPlaceholder) {
|
||||
if(!compilerOptions.floats) {
|
||||
if (target.name == "peekf" || target.name == "pokef")
|
||||
errors.err("floating point used, but that is not enabled via options", position)
|
||||
}
|
||||
}
|
||||
|
||||
if(target is Label && args.isNotEmpty())
|
||||
errors.err("cannot use arguments when calling a label", position)
|
||||
|
||||
@@ -1263,6 +1304,10 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.forEach{
|
||||
checkLongType(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||
@@ -1300,6 +1345,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
checkLongType(arrayIndexedExpression)
|
||||
val target = arrayIndexedExpression.arrayvar.targetStatement(program)
|
||||
if(target is VarDecl) {
|
||||
if(target.datatype !in IterableDatatypes && target.datatype!=DataType.UWORD)
|
||||
@@ -1338,7 +1384,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(choices!=null) {
|
||||
for (c in choices) {
|
||||
if(c in tally)
|
||||
errors.err("choice value already occurs earlier", choiceNode.position)
|
||||
errors.err("choice value already occurs elsewhere", choiceNode.position)
|
||||
else
|
||||
tally.add(c)
|
||||
}
|
||||
@@ -1358,18 +1404,18 @@ internal class AstChecker(private val program: Program,
|
||||
val whenStmt = whenChoice.parent as When
|
||||
if(whenChoice.values!=null) {
|
||||
val conditionType = whenStmt.condition.inferType(program)
|
||||
val constvalues = whenChoice.values!!.map { it.constValue(program) }
|
||||
for(constvalue in constvalues) {
|
||||
val constvalues = whenChoice.values!!.map { it.constValue(program) to it.position }
|
||||
for((constvalue, pos) in constvalues) {
|
||||
when {
|
||||
constvalue == null -> errors.err("choice value must be a constant", whenChoice.position)
|
||||
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position)
|
||||
constvalue == null -> errors.err("choice value must be a constant", pos)
|
||||
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", pos)
|
||||
conditionType isnot constvalue.type -> {
|
||||
if(conditionType.isKnown) {
|
||||
if(conditionType.istype(DataType.BOOL)) {
|
||||
if(constvalue.number!=0.0 && constvalue.number!=1.0)
|
||||
errors.err("choice value datatype differs from condition value", whenChoice.position)
|
||||
errors.err("choice value datatype differs from condition value", pos)
|
||||
} else {
|
||||
errors.err("choice value datatype differs from condition value", whenChoice.position)
|
||||
errors.err("choice value datatype differs from condition value", pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1444,6 +1490,12 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("%asm containing IR code cannot be translated to 6502 assembly", inlineAssembly.position)
|
||||
}
|
||||
|
||||
private fun checkLongType(expression: Expression) {
|
||||
if(expression.inferType(program).istype(DataType.LONG)) {
|
||||
errors.err("integer overflow", expression.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
|
||||
return if (targetDt == DataType.STR) {
|
||||
when {
|
||||
|
||||
@@ -61,10 +61,6 @@ internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, err
|
||||
val walker = object : AstWalker() {
|
||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
require(char.encoding != Encoding.DEFAULT)
|
||||
if(char.encoding != Encoding.DEFAULT && char.encoding !in target.supportedEncodings) {
|
||||
errors.err("compilation target doesn't support this text encoding", char.position)
|
||||
return noModifications
|
||||
}
|
||||
return try {
|
||||
val encoded = target.encodeString(char.value.toString(), char.encoding)
|
||||
listOf(IAstModification.ReplaceNode(
|
||||
@@ -81,10 +77,6 @@ internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, err
|
||||
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
|
||||
// this only *checks* for errors for string encoding. The actual encoding is done much later
|
||||
require(string.encoding != Encoding.DEFAULT)
|
||||
if(string.encoding != Encoding.DEFAULT && string.encoding !in target.supportedEncodings) {
|
||||
errors.err("compilation target doesn't support this text encoding", string.position)
|
||||
return noModifications
|
||||
}
|
||||
try {
|
||||
target.encodeString(string.value, string.encoding)
|
||||
} catch (x: CharConversionException) {
|
||||
|
||||
@@ -33,10 +33,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
override fun visit(block: Block) {
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null) {
|
||||
if(block.isInLibrary)
|
||||
nameError(existing.name, existing.position, block)
|
||||
else
|
||||
nameError(block.name, block.position, existing)
|
||||
// we allow duplicates if at least one of them has %option merge
|
||||
if("merge" !in existing.options() + block.options()) {
|
||||
if (block.isInLibrary)
|
||||
nameError(existing.name, existing.position, block)
|
||||
else
|
||||
nameError(block.name, block.position, existing)
|
||||
}
|
||||
}
|
||||
else
|
||||
blocks[block.name] = block
|
||||
@@ -50,16 +53,18 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
if(decl.name in BuiltinFunctions)
|
||||
errors.err("builtin function cannot be redefined", decl.position)
|
||||
|
||||
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
|
||||
if(existingInSameScope!=null && existingInSameScope!==decl)
|
||||
nameError(decl.name, decl.position, existingInSameScope)
|
||||
if(decl.names.size<2) {
|
||||
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
|
||||
if (existingInSameScope != null && existingInSameScope !== decl)
|
||||
nameError(decl.name, decl.position, existingInSameScope)
|
||||
|
||||
val existingOuter = decl.parent.definingScope.lookup(listOf(decl.name))
|
||||
if (existingOuter != null && existingOuter !== decl && existingOuter is VarDecl) {
|
||||
if(existingOuter.parent!==decl.parent)
|
||||
nameShadowWarning(decl.name, decl.position, existingOuter)
|
||||
else
|
||||
nameError(decl.name, decl.position, existingOuter)
|
||||
val existingOuter = decl.parent.definingScope.lookup(listOf(decl.name))
|
||||
if (existingOuter != null && existingOuter !== decl && existingOuter is VarDecl) {
|
||||
if (existingOuter.parent !== decl.parent)
|
||||
nameShadowWarning(decl.name, decl.position, existingOuter)
|
||||
else
|
||||
nameError(decl.name, decl.position, existingOuter)
|
||||
}
|
||||
}
|
||||
|
||||
if(decl.definingBlock.name==decl.name)
|
||||
@@ -174,7 +179,8 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
else
|
||||
'_'
|
||||
}.joinToString("")
|
||||
call.args[0] = StringLiteral(processed, compTarget.defaultEncoding, name.position)
|
||||
val textEncoding = (call as Node).definingModule.textEncoding
|
||||
call.args[0] = StringLiteral(processed, textEncoding, name.position)
|
||||
call.args[0].linkParents(call as Node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,13 +58,13 @@ class AstPreprocessor(val program: Program,
|
||||
|
||||
override fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(char.encoding== Encoding.DEFAULT)
|
||||
char.encoding = options.compTarget.defaultEncoding
|
||||
char.encoding = char.definingModule.textEncoding
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(string: StringLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(string.encoding==Encoding.DEFAULT)
|
||||
string.encoding = options.compTarget.defaultEncoding
|
||||
string.encoding = string.definingModule.textEncoding
|
||||
return super.before(string, parent)
|
||||
}
|
||||
|
||||
@@ -117,16 +117,36 @@ class AstPreprocessor(val program: Program,
|
||||
replacements.add(IAstModification.Remove(decl, scope))
|
||||
} else {
|
||||
val declToInsert: VarDecl
|
||||
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
declToInsert = decl.copy()
|
||||
if(decl.names.size>1) {
|
||||
// we need to handle multi-decl here too, the desugarer maybe has not processed it here yet...
|
||||
if(decl.value!=null) {
|
||||
decl.names.forEach { name ->
|
||||
val target = AssignTarget(IdentifierReference(listOf(name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target.copy(), decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(IAstModification.InsertAfter(decl, assign, scope))
|
||||
}
|
||||
replacements.add(IAstModification.Remove(decl, scope))
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
declToInsert = decl
|
||||
} else {
|
||||
// just move it to the defining scope
|
||||
replacements.add(IAstModification.Remove(decl, scope))
|
||||
declToInsert = decl
|
||||
}
|
||||
} else {
|
||||
replacements.add(IAstModification.Remove(decl, scope))
|
||||
declToInsert = decl
|
||||
// handle declaration of a single variable
|
||||
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
declToInsert = decl.copy()
|
||||
} else {
|
||||
replacements.add(IAstModification.Remove(decl, scope))
|
||||
declToInsert = decl
|
||||
}
|
||||
}
|
||||
movements.add(IAstModification.InsertFirst(declToInsert, parentscope))
|
||||
}
|
||||
@@ -177,7 +197,7 @@ class AstPreprocessor(val program: Program,
|
||||
private fun makeSplitArray(decl: VarDecl): Iterable<IAstModification> {
|
||||
val splitDt = ArrayToElementTypes.getValue(decl.datatype)
|
||||
val newDecl = VarDecl(
|
||||
decl.type, decl.origin, splitDt, decl.zeropage, decl.arraysize, decl.name,
|
||||
decl.type, decl.origin, splitDt, decl.zeropage, decl.arraysize, decl.name, emptyList(),
|
||||
decl.value, true, decl.sharedWithAsm, true, decl.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(decl, newDecl, decl.parent))
|
||||
|
||||
@@ -29,7 +29,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
|
||||
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,
|
||||
val ubyteDecl = VarDecl(decl.type, decl.origin, DataType.UBYTE, decl.zeropage, decl.arraysize, decl.name, emptyList(),
|
||||
newvalue, decl.isArray, decl.sharedWithAsm, decl.splitArray, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl, ubyteDecl, parent))
|
||||
}
|
||||
@@ -46,7 +46,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
|
||||
}.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,
|
||||
val ubyteArrayDecl = VarDecl(decl.type, decl.origin, DataType.ARRAY_UB, decl.zeropage, decl.arraysize, decl.name, emptyList(),
|
||||
newarray, true, decl.sharedWithAsm, decl.splitArray, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent))
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
|
||||
// have been performed (because those could re-introduce nodes that have to be desugared)
|
||||
//
|
||||
// List of modifications:
|
||||
// - replace 'break' statements by a goto + generated after label.
|
||||
// - replace 'break' and 'continue' statements by a goto + generated after label.
|
||||
// - replace while and do-until loops by just jumps.
|
||||
// - replace peek() and poke() by direct memory accesses.
|
||||
// - repeat-forever loops replaced by label+jump.
|
||||
// - pointer[word] replaced by @(pointer+word)
|
||||
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
|
||||
|
||||
// - flatten chained assignments
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
|
||||
@@ -52,6 +52,39 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(continueStmt: Continue, parent: Node): Iterable<IAstModification> {
|
||||
fun jumpToBottom(scope: IStatementContainer): Iterable<IAstModification> {
|
||||
val label = program.makeLabel("cont", continueStmt.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(continueStmt, program.jumpLabel(label), parent),
|
||||
IAstModification.InsertLast(label, scope)
|
||||
)
|
||||
}
|
||||
|
||||
fun jumpToBefore(loop: WhileLoop): Iterable<IAstModification> {
|
||||
val label = program.makeLabel("cont", continueStmt.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(continueStmt, program.jumpLabel(label), parent),
|
||||
IAstModification.InsertBefore(loop, label, loop.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
|
||||
var partof = parent
|
||||
while(true) {
|
||||
when (partof) {
|
||||
is Subroutine, is Block, is ParentSentinel -> {
|
||||
errors.err("continue in wrong scope", continueStmt.position)
|
||||
return noModifications
|
||||
}
|
||||
is ForLoop -> return jumpToBottom(partof.body)
|
||||
is RepeatLoop -> return jumpToBottom(partof.body)
|
||||
is UntilLoop -> return jumpToBottom(partof.body)
|
||||
is WhileLoop -> return jumpToBefore(partof)
|
||||
else -> partof = partof.parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
/*
|
||||
do { STUFF } until CONDITION
|
||||
@@ -77,6 +110,22 @@ if CONDITION==0
|
||||
}
|
||||
|
||||
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
/*
|
||||
while true -> repeat
|
||||
while false -> discard
|
||||
*/
|
||||
|
||||
val constCondition = whileLoop.condition.constValue(program)?.asBooleanValue
|
||||
if(constCondition==true) {
|
||||
errors.warn("condition is always true", whileLoop.condition.position)
|
||||
val repeat = RepeatLoop(null, whileLoop.body, whileLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop, repeat, parent))
|
||||
} else if(constCondition==false) {
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
return listOf(IAstModification.Remove(whileLoop, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
while CONDITION { STUFF }
|
||||
==>
|
||||
@@ -238,4 +287,33 @@ _after:
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(chainedAssignment: ChainedAssignment, parent: Node): Iterable<IAstModification> {
|
||||
val assign = chainedAssignment.nested as? Assignment
|
||||
if(assign!=null) {
|
||||
// unpack starting from last in the chain
|
||||
val assigns = mutableListOf<Statement>(assign)
|
||||
var lastChained: ChainedAssignment = chainedAssignment
|
||||
var pc: ChainedAssignment? = chainedAssignment
|
||||
|
||||
if(assign.value.isSimple) {
|
||||
// simply copy the RHS value to each component's assignment
|
||||
while (pc != null) {
|
||||
lastChained = pc
|
||||
assigns.add(Assignment(pc.target.copy(), assign.value.copy(), assign.origin, pc.position))
|
||||
pc = pc.parent as? ChainedAssignment
|
||||
}
|
||||
} else if(pc!=null) {
|
||||
// need to evaluate RHS once and reuse that in each component's assignment
|
||||
val firstComponentAsValue = assign.target.toExpression()
|
||||
while (pc != null) {
|
||||
lastChained = pc
|
||||
assigns.add(Assignment(pc.target.copy(), firstComponentAsValue.copy(), assign.origin, pc.position))
|
||||
pc = pc.parent as? ChainedAssignment
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(lastChained,
|
||||
AnonymousScope(assigns, chainedAssignment.position), lastChained.parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,11 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||
private fun transformStatement(statement: Statement): PtNode {
|
||||
return when (statement) {
|
||||
is AnonymousScope -> throw FatalAstException("AnonymousScopes should have been flattened")
|
||||
is ChainedAssignment -> throw FatalAstException("ChainedAssignment should have been flattened")
|
||||
is Assignment -> transform(statement)
|
||||
is Block -> transform(statement)
|
||||
is Break -> throw FatalAstException("break should have been replaced by Goto")
|
||||
is Continue -> throw FatalAstException("continue should have been replaced by Goto")
|
||||
is BuiltinFunctionCallStatement -> transform(statement)
|
||||
is BuiltinFunctionPlaceholder -> throw FatalAstException("BuiltinFunctionPlaceholder should not occur in Ast here")
|
||||
is ConditionalBranch -> transform(statement)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.ArrayLiteral
|
||||
@@ -21,10 +22,6 @@ internal class LiteralsToAutoVars(private val program: Program,
|
||||
) : AstWalker() {
|
||||
|
||||
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(string.encoding != Encoding.DEFAULT && string.encoding !in target.supportedEncodings) {
|
||||
errors.err("compilation target doesn't support this text encoding", string.position)
|
||||
return noModifications
|
||||
}
|
||||
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||
val binExpr = string.parent as? BinaryExpression
|
||||
if(binExpr!=null &&(binExpr.operator=="+" || binExpr.operator=="*"))
|
||||
@@ -74,4 +71,20 @@ internal class LiteralsToAutoVars(private val program: Program,
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.names.size>1) {
|
||||
// note: the desugaring of a multi-variable vardecl has to be done here
|
||||
// and not in CodeDesugarer, that one is too late (identifiers can't be found otherwise)
|
||||
if(decl.datatype !in NumericDatatypes)
|
||||
errors.err("can only multi declare numeric variables", decl.position)
|
||||
if(errors.noErrors()) {
|
||||
// desugar into individual vardecl per name.
|
||||
return decl.desugarMultiDecl().map {
|
||||
IAstModification.InsertBefore(decl, it, parent as IStatementContainer)
|
||||
} + IAstModification.Remove(decl, parent as IStatementContainer)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ internal class StatementReorderer(
|
||||
// - sorts the choices in when statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
|
||||
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address", "%option")
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address", "%option", "%encoding")
|
||||
|
||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
@@ -155,6 +155,7 @@ internal class StatementReorderer(
|
||||
it.zeropage,
|
||||
null,
|
||||
it.name,
|
||||
emptyList(),
|
||||
null,
|
||||
false,
|
||||
it.sharedWithAsm,
|
||||
@@ -191,11 +192,6 @@ internal class StatementReorderer(
|
||||
errors.err("else choice must be the last one", whenStmt.choices[elseChoice].position)
|
||||
}
|
||||
|
||||
val choices = whenStmt.choiceValues(program).sortedBy {
|
||||
it.first?.first() ?: Int.MAX_VALUE
|
||||
}
|
||||
whenStmt.choices.clear()
|
||||
choices.mapTo(whenStmt.choices) { it.second }
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -364,34 +364,17 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
|
||||
override fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> {
|
||||
val conditionDt = (whenChoice.parent as When).condition.inferType(program)
|
||||
if((parent as When).condition.inferType(program).isWords) {
|
||||
val values = whenChoice.values
|
||||
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
|
||||
val valueDt = value.inferType(program)
|
||||
if(valueDt!=conditionDt) {
|
||||
val castedValue = (value as NumericLiteral).cast(conditionDt.getOr(DataType.UNDEFINED))
|
||||
if(castedValue.isValid) {
|
||||
values[index] = castedValue.valueOrZero()
|
||||
} else {
|
||||
errors.err("choice value datatype differs from condition value", value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val values = whenChoice.values
|
||||
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
|
||||
val valueDt = value.inferType(program)
|
||||
if(valueDt!=conditionDt) {
|
||||
val castedValue = (value as NumericLiteral).cast(conditionDt.getOr(DataType.UNDEFINED))
|
||||
if(castedValue.isValid) {
|
||||
values[index] = castedValue.valueOrZero()
|
||||
} else {
|
||||
errors.err("choice value datatype differs from condition value", value.position)
|
||||
}
|
||||
val values = whenChoice.values
|
||||
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
|
||||
val valueDt = value.inferType(program)
|
||||
if(valueDt!=conditionDt) {
|
||||
val castedValue = value.typecastTo(conditionDt.getOr(DataType.UNDEFINED), valueDt.getOr(DataType.UNDEFINED), true)
|
||||
if(castedValue.first) {
|
||||
castedValue.second.linkParents(whenChoice)
|
||||
values[index] = castedValue.second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -47,9 +47,14 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
else {
|
||||
if(functionCallExpr.target.nameInSource==listOf("memory")) {
|
||||
val name = (functionCallExpr.args[0] as StringLiteral).value
|
||||
val size = (functionCallExpr.args[1] as NumericLiteral).number.toInt()
|
||||
val align = (functionCallExpr.args[2] as NumericLiteral).number.toInt()
|
||||
memorySlabs.add(Slab(name, size, align, functionCallExpr.position))
|
||||
val size = (functionCallExpr.args[1] as? NumericLiteral)?.number?.toInt()
|
||||
val align = (functionCallExpr.args[2] as? NumericLiteral)?.number?.toInt()
|
||||
if(size==null)
|
||||
errors.err("argument must be a constant", functionCallExpr.args[1].position)
|
||||
if(align==null)
|
||||
errors.err("argument must be a constant", functionCallExpr.args[2].position)
|
||||
if(size!=null && align!=null)
|
||||
memorySlabs.add(Slab(name, size, align, functionCallExpr.position))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
@@ -110,7 +112,7 @@ class TestAstChecks: FunSpec({
|
||||
compileText(C64Target(), true, text, writeAssembly = true, errors=errors)
|
||||
errors.errors.size shouldBe 1
|
||||
errors.warnings.size shouldBe 0
|
||||
errors.errors[0] shouldContain "const modifier can only be used"
|
||||
errors.errors[0] shouldContain "const can only be used"
|
||||
}
|
||||
|
||||
test("array indexing is not allowed on a memory mapped variable") {
|
||||
@@ -145,4 +147,31 @@ class TestAstChecks: FunSpec({
|
||||
errors.errors.size shouldBe 0
|
||||
errors.warnings.size shouldBe 0
|
||||
}
|
||||
|
||||
test("unicode in identifier names is working") {
|
||||
val text = """
|
||||
%import floats
|
||||
|
||||
main {
|
||||
ubyte приблизительно = 99
|
||||
ubyte นี่คือตัวอักษรภาษาไท = 42
|
||||
|
||||
sub start() {
|
||||
str knäckebröd = "crunchy" ; with composed form
|
||||
prt(knäckebröd) ; with decomposed form
|
||||
printf(2*floats.π)
|
||||
}
|
||||
|
||||
sub prt(str message) {
|
||||
приблизительно++
|
||||
}
|
||||
|
||||
sub printf(float fl) {
|
||||
นี่คือตัวอักษรภาษาไท++
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(Cx16Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -5,6 +5,7 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.code.core.BuiltinFunctions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.NumericDatatypesNoBool
|
||||
@@ -85,26 +86,31 @@ class TestBuiltinFunctions: FunSpec({
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
uword[] array = [1,2,3]
|
||||
str name = "hello"
|
||||
cx16.r0L = len(array)
|
||||
cx16.r0L = len(name)
|
||||
cx16.r0L = sizeof(array)
|
||||
cx16.r0 = mkword(200,100)
|
||||
cx16.r1L = len(name)
|
||||
cx16.r2L = sizeof(array)
|
||||
cx16.r4 = mkword(200,100)
|
||||
test(sizeof(array))
|
||||
}
|
||||
sub test(uword value) {
|
||||
value++
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), false, src, writeAssembly = false)
|
||||
val statements = result!!.compilerAst.entrypoint.statements
|
||||
statements.size shouldBe 6
|
||||
statements.size shouldBe 7
|
||||
val a1 = statements[2] as Assignment
|
||||
val a2 = statements[3] as Assignment
|
||||
val a3 = statements[4] as Assignment
|
||||
val a4 = statements[5] as Assignment
|
||||
val a5 = statements[6] as FunctionCallStatement
|
||||
(a1.value as NumericLiteral).number shouldBe 3.0
|
||||
(a2.value as NumericLiteral).number shouldBe 5.0
|
||||
(a3.value as NumericLiteral).number shouldBe 6.0
|
||||
(a4.value as NumericLiteral).number shouldBe 200*256+100
|
||||
(a5.args[0] as NumericLiteral).number shouldBe 6.0
|
||||
}
|
||||
})
|
||||
|
||||
@@ -117,8 +117,11 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"kefrenbars",
|
||||
"keyboardhandler",
|
||||
"mandelbrot",
|
||||
"multi-irq-old",
|
||||
"multi-irq-new",
|
||||
"plasma",
|
||||
"rasterbars",
|
||||
"showbmx",
|
||||
"snow",
|
||||
"spotlight",
|
||||
"tehtriz",
|
||||
|
||||
@@ -3,10 +3,12 @@ package prog8tests
|
||||
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.shouldStartWith
|
||||
import prog8.code.core.ZeropageType
|
||||
import prog8.code.core.internedStringsModuleName
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.compiler.determineCompilationOptions
|
||||
import prog8.compiler.parseMainModule
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
@@ -108,4 +110,29 @@ main {
|
||||
}
|
||||
}
|
||||
|
||||
test("merge option works on library modules") {
|
||||
val src="""
|
||||
%zeropage basicsafe
|
||||
%import textio
|
||||
|
||||
txt {
|
||||
%option merge
|
||||
sub println(uword string) {
|
||||
txt.print(string)
|
||||
txt.nl()
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
txt.println("Hello, world1")
|
||||
txt.println("Hello, world2")
|
||||
txt.println("Hello, world3")
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), optimize = false, src) shouldNotBe null
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
@@ -112,7 +112,7 @@ class TestMemory: FunSpec({
|
||||
}
|
||||
|
||||
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
@@ -150,7 +150,7 @@ class TestMemory: FunSpec({
|
||||
}
|
||||
|
||||
test("regular variable not in mapped IO ram on C64") {
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), null, false, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
@@ -162,7 +162,7 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("memory mapped variable not in mapped IO ram on C64") {
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
@@ -174,7 +174,7 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("memory mapped variable in mapped IO ram on C64") {
|
||||
val address = 0xd020u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
@@ -185,7 +185,7 @@ class TestMemory: FunSpec({
|
||||
}
|
||||
|
||||
test("array not in mapped IO ram") {
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), null, false, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
@@ -198,7 +198,7 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("memory mapped array not in mapped IO ram") {
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
@@ -211,7 +211,7 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("memory mapped array in mapped IO ram") {
|
||||
val address = 0xd800u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
|
||||
@@ -123,7 +123,7 @@ class TestNumbers: FunSpec({
|
||||
uword xx = 10
|
||||
if xx+99 == 1.23456
|
||||
xx++
|
||||
if xx+99 == 1234567
|
||||
if xx+99 == 1234567.8
|
||||
xx++
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ class TestNumbers: FunSpec({
|
||||
uword xx = 10
|
||||
if xx+99 == 1.23456
|
||||
xx++
|
||||
if xx+99 == 1234567
|
||||
if xx+99 == 1234567.8
|
||||
xx++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.*
|
||||
|
||||
|
||||
@@ -150,7 +151,66 @@ class TestOptimization: FunSpec({
|
||||
binexpr7.right shouldBe NumericLiteral(DataType.UWORD, 99.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("const folding multiple scenarios * and /") {
|
||||
test("const folding multiple scenarios * and / (floats)") {
|
||||
val source = """
|
||||
%option enable_floats
|
||||
main {
|
||||
sub start() {
|
||||
float llw = 300.0
|
||||
float result
|
||||
result = 9 * 2 * 10 * llw
|
||||
result++
|
||||
result = llw * 9 * 2 * 10
|
||||
result++
|
||||
result = llw / 30 / 3
|
||||
result++
|
||||
result = llw / 2 * 10
|
||||
result++
|
||||
result = llw * 90 / 5
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), true, source, writeAssembly = false)!!
|
||||
// expected:
|
||||
// float llw
|
||||
// llw = 300.0
|
||||
// float result
|
||||
// result = llw * 180.0
|
||||
// result++
|
||||
// result = llw * 180.0
|
||||
// result++
|
||||
// result = llw / 90.0
|
||||
// result++
|
||||
// result = llw * 5.0
|
||||
// result++
|
||||
// result = llw * 18.0
|
||||
val stmts = result.compilerAst.entrypoint.statements
|
||||
stmts.size shouldBe 12
|
||||
|
||||
val mulR0Value = (stmts[3] as Assignment).value
|
||||
val binexpr0 = mulR0Value as BinaryExpression
|
||||
binexpr0.operator shouldBe "*"
|
||||
binexpr0.right shouldBe NumericLiteral(DataType.FLOAT, 180.0, Position.DUMMY)
|
||||
val mulR1Value = (stmts[5] as Assignment).value
|
||||
val binexpr1 = mulR1Value as BinaryExpression
|
||||
binexpr1.operator shouldBe "*"
|
||||
binexpr1.right shouldBe NumericLiteral(DataType.FLOAT, 180.0, Position.DUMMY)
|
||||
val divR2Value = (stmts[7] as Assignment).value
|
||||
val binexpr2 = divR2Value as BinaryExpression
|
||||
binexpr2.operator shouldBe "/"
|
||||
binexpr2.right shouldBe NumericLiteral(DataType.FLOAT, 90.0, Position.DUMMY)
|
||||
val mulR3Value = (stmts[9] as Assignment).value
|
||||
val binexpr3 = mulR3Value as BinaryExpression
|
||||
binexpr3.operator shouldBe "*"
|
||||
binexpr3.right shouldBe NumericLiteral(DataType.FLOAT, 5.0, Position.DUMMY)
|
||||
binexpr3.left shouldBe instanceOf<IdentifierReference>()
|
||||
val mulR4Value = (stmts[11] as Assignment).value
|
||||
val binexpr4 = mulR4Value as BinaryExpression
|
||||
binexpr4.operator shouldBe "*"
|
||||
binexpr4.right shouldBe NumericLiteral(DataType.FLOAT, 18.0, Position.DUMMY)
|
||||
binexpr4.left shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
|
||||
test("const folding multiple scenarios * and / (integers)") {
|
||||
val source = """
|
||||
main {
|
||||
sub start() {
|
||||
@@ -169,8 +229,8 @@ class TestOptimization: FunSpec({
|
||||
// cx16.r0s = llw * 180
|
||||
// cx16.r1s = llw * 180
|
||||
// cx16.r2s = llw / 90
|
||||
// cx16.r3s = llw * 5
|
||||
// cx16.r4s = llw * 90 / 5
|
||||
// cx16.r3s = llw /2 *10
|
||||
// cx16.r4s = llw *90 /5
|
||||
val stmts = result.compilerAst.entrypoint.statements
|
||||
stmts.size shouldBe 7
|
||||
|
||||
@@ -189,11 +249,13 @@ class TestOptimization: FunSpec({
|
||||
val mulR3Value = (stmts[5] as Assignment).value
|
||||
val binexpr3 = mulR3Value as BinaryExpression
|
||||
binexpr3.operator shouldBe "*"
|
||||
binexpr3.right shouldBe NumericLiteral(DataType.UWORD, 5.0, Position.DUMMY)
|
||||
binexpr3.right shouldBe NumericLiteral(DataType.UWORD, 10.0, Position.DUMMY)
|
||||
binexpr3.left shouldBe instanceOf<BinaryExpression>()
|
||||
val mulR4Value = (stmts[6] as Assignment).value
|
||||
val binexpr4 = mulR4Value as BinaryExpression
|
||||
binexpr4.operator shouldBe "/"
|
||||
binexpr4.right shouldBe NumericLiteral(DataType.UWORD, 5.0, Position.DUMMY)
|
||||
binexpr4.left shouldBe instanceOf<BinaryExpression>()
|
||||
}
|
||||
|
||||
test("constantfolded and silently typecasted for initializervalues") {
|
||||
@@ -758,4 +820,61 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), true, text, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("replacing string print by chrout with referenced string elsewhere shouldn't give string symbol error") {
|
||||
val text="""
|
||||
%import textio
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
str key = "test"
|
||||
txt.print(":")
|
||||
if key != ":" {
|
||||
cx16.r0++
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(VMTarget(), true, text, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("sub only called by asm should not be optimized away") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
%asm{{
|
||||
jsr p8_test
|
||||
}}
|
||||
}
|
||||
|
||||
sub test() {
|
||||
cx16.r0++
|
||||
}
|
||||
}"""
|
||||
compileText(Cx16Target(), true, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("no crash for making var in removed/inlined subroutine fully scoped") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
test()
|
||||
}
|
||||
|
||||
sub test() {
|
||||
sub nested() {
|
||||
ubyte counter
|
||||
counter++
|
||||
}
|
||||
test2(main.test.nested.counter) ; shouldn't crash but just give nice error
|
||||
}
|
||||
|
||||
sub test2(ubyte value) {
|
||||
value++
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target(), true, src, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.single() shouldContain "undefined symbol"
|
||||
}
|
||||
})
|
||||
@@ -7,7 +7,10 @@ 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 prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.unescape
|
||||
import prog8.code.target.C64Target
|
||||
@@ -260,7 +263,7 @@ class TestStringEncodings: FunSpec({
|
||||
errors.errors.size shouldBe 0
|
||||
}
|
||||
|
||||
test("unsupported string encoding iso for C64 compilationtarget") {
|
||||
test("iso string encoding also on C64 compilationtarget") {
|
||||
val source="""
|
||||
main {
|
||||
str string1 = "default"
|
||||
@@ -271,12 +274,11 @@ class TestStringEncodings: FunSpec({
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, source, errors, writeAssembly = false) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "text encoding"
|
||||
compileText(C64Target(), false, source, errors, writeAssembly = false) shouldNotBe null
|
||||
errors.errors.size shouldBe 0
|
||||
}
|
||||
|
||||
test("unsupported char encoding iso for C64 compilationtarget") {
|
||||
test("iso char encoding also on C64 compilationtarget") {
|
||||
val source="""
|
||||
main {
|
||||
ubyte char1 = 'd'
|
||||
@@ -287,9 +289,8 @@ class TestStringEncodings: FunSpec({
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, source, errors, writeAssembly = false) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "text encoding"
|
||||
compileText(C64Target(), false, source, errors, writeAssembly = false) shouldNotBe null
|
||||
errors.errors.size shouldBe 0
|
||||
}
|
||||
|
||||
test("all encodings supported for Cx16 target") {
|
||||
@@ -310,5 +311,60 @@ class TestStringEncodings: FunSpec({
|
||||
}"""
|
||||
compileText(Cx16Target(), false, source, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("module level no default encoding thus petscii") {
|
||||
val source="""
|
||||
main {
|
||||
str string1 = "default"
|
||||
str string2 = petscii:"petscii"
|
||||
|
||||
ubyte char1 = 'd'
|
||||
ubyte char2 = petscii:'p'
|
||||
|
||||
sub start() {
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), false, source, writeAssembly = false)!!
|
||||
val main = result.compilerAst.entrypoint.definingBlock
|
||||
main.statements.size shouldBe 7
|
||||
val string1 = (main.statements[0] as VarDecl).value as StringLiteral
|
||||
string1.encoding shouldBe Encoding.PETSCII
|
||||
string1.value shouldBe "default"
|
||||
val string2 = (main.statements[1] as VarDecl).value as StringLiteral
|
||||
string2.encoding shouldBe Encoding.PETSCII
|
||||
string2.value shouldBe "petscii"
|
||||
val char1 = (main.statements[3] as Assignment).value as NumericLiteral
|
||||
char1.number shouldBe 68.0
|
||||
val char2 = (main.statements[5] as Assignment).value as NumericLiteral
|
||||
char2.number shouldBe 80.0
|
||||
}
|
||||
|
||||
test("module level default encoding iso") {
|
||||
val source="""
|
||||
%encoding iso
|
||||
main {
|
||||
str string1 = "default"
|
||||
str string2 = petscii:"petscii"
|
||||
|
||||
ubyte char1 = 'd'
|
||||
ubyte char2 = petscii:'p'
|
||||
|
||||
sub start() {
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), false, source, writeAssembly = false)!!
|
||||
val main = result.compilerAst.entrypoint.definingBlock
|
||||
main.statements.size shouldBe 7
|
||||
val string1 = (main.statements[0] as VarDecl).value as StringLiteral
|
||||
string1.encoding shouldBe Encoding.ISO
|
||||
string1.value shouldBe "default"
|
||||
val string2 = (main.statements[1] as VarDecl).value as StringLiteral
|
||||
string2.encoding shouldBe Encoding.PETSCII
|
||||
string2.value shouldBe "petscii"
|
||||
val char1 = (main.statements[3] as Assignment).value as NumericLiteral
|
||||
char1.number shouldBe 100.0
|
||||
val char2 = (main.statements[5] as Assignment).value as NumericLiteral
|
||||
char2.number shouldBe 80.0
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1043,4 +1043,31 @@ main {
|
||||
errors.errors.single() shouldContain "doesn't match"
|
||||
}
|
||||
|
||||
test("long type okay in const expr but otherwise overflow") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
const ubyte HEIGHT=240
|
||||
uword large = 320*240/8/8
|
||||
thing(large)
|
||||
thing(320*240/8/8)
|
||||
thing(320*HEIGHT/8/8)
|
||||
thing(320*HEIGHT) ; overflow
|
||||
large = 12345678 ; overflow
|
||||
}
|
||||
|
||||
sub thing(uword value) {
|
||||
value++
|
||||
}
|
||||
}"""
|
||||
val errors=ErrorReporterForTests()
|
||||
compileText(C64Target(), false, src, writeAssembly = false, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 5
|
||||
errors.errors[0] shouldContain "can't cast"
|
||||
errors.errors[1] shouldContain "overflow"
|
||||
errors.errors[2] shouldContain "LONG doesn't match"
|
||||
errors.errors[3] shouldContain "out of range"
|
||||
errors.errors[4] shouldContain "overflow"
|
||||
}
|
||||
|
||||
})
|
||||
@@ -1025,4 +1025,25 @@ main {
|
||||
compileText(VMTarget(), false, src, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("underscores for numeric groupings") {
|
||||
val src="""
|
||||
%option enable_floats
|
||||
main {
|
||||
sub start() {
|
||||
uword w1 = 000_1234_5__
|
||||
uword w2 = ${'$'}ff_ee
|
||||
uword w3 = %11_0000_111111__0000
|
||||
float fl = 3_000_001.141_592_654
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, writeAssembly = false)!!
|
||||
val st = result.compilerAst.entrypoint.statements
|
||||
st.size shouldBe 8
|
||||
val assigns = st.filterIsInstance<Assignment>()
|
||||
(assigns[0].value as NumericLiteral).number shouldBe 12345
|
||||
(assigns[1].value as NumericLiteral).number shouldBe 0xffee
|
||||
(assigns[2].value as NumericLiteral).number shouldBe 0b1100001111110000
|
||||
(assigns[3].value as NumericLiteral).number shouldBe 3000001.141592654
|
||||
}
|
||||
|
||||
})
|
||||
@@ -8,6 +8,7 @@ import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.DataType
|
||||
@@ -379,5 +380,65 @@ main {
|
||||
}"""
|
||||
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("sizeof number const evaluation in vardecl") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared size1 = sizeof(22222)
|
||||
uword @shared size2 = sizeof(2.2)
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("multi-var decls in scope with initializer") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte w
|
||||
|
||||
for w in 0 to 20 {
|
||||
ubyte @zp x,y,z=13
|
||||
ubyte q,r,s
|
||||
x++
|
||||
y++
|
||||
z++
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), optimize = false, src, writeAssembly = false)!!
|
||||
val st = result.compilerAst.entrypoint.statements
|
||||
/*
|
||||
sub start () {
|
||||
ubyte s
|
||||
s = 0
|
||||
ubyte r
|
||||
r = 0
|
||||
ubyte q
|
||||
q = 0
|
||||
ubyte @zp z
|
||||
ubyte @zp y
|
||||
ubyte @zp x
|
||||
ubyte w
|
||||
for w in 0 to 20 step 1 {
|
||||
z = 13
|
||||
y = 13
|
||||
x = 13
|
||||
x++
|
||||
y++
|
||||
z++
|
||||
}
|
||||
}
|
||||
*/
|
||||
val vars = st.filterIsInstance<VarDecl>()
|
||||
vars.size shouldBe 7
|
||||
vars.all { it.names.size<=1 } shouldBe true
|
||||
vars.map { it.name }.toSet() shouldBe setOf("s","r","q","z","y","x","w")
|
||||
val forloop = st.single { it is ForLoop } as ForLoop
|
||||
forloop.body.statements[0] shouldBe instanceOf<Assignment>()
|
||||
forloop.body.statements[1] shouldBe instanceOf<Assignment>()
|
||||
forloop.body.statements[2] shouldBe instanceOf<Assignment>()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -175,5 +175,23 @@ main {
|
||||
assembly shouldContain "thearray_lsb"
|
||||
assembly shouldContain "thearray_msb"
|
||||
}
|
||||
|
||||
test("indexing str or pointervar with expression") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
str name = "thing"
|
||||
modify(name)
|
||||
|
||||
sub modify(str arg) {
|
||||
ubyte n=1
|
||||
uword pointervar
|
||||
arg[n+1] = arg[1]
|
||||
pointervar[n+1] = pointervar[1]
|
||||
}
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -46,8 +46,8 @@ class TestAsmGenSymbols: StringSpec({
|
||||
}
|
||||
|
||||
*/
|
||||
val varInSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteral.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val var2InSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, Position.DUMMY)
|
||||
val varInSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", emptyList(), NumericLiteral.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val var2InSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", emptyList(), null, false, false, false, Position.DUMMY)
|
||||
val labelInSub = Label("locallabel", Position.DUMMY)
|
||||
|
||||
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
@@ -63,7 +63,7 @@ class TestAsmGenSymbols: StringSpec({
|
||||
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
|
||||
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, statements, Position.DUMMY)
|
||||
val labelInBlock = Label("label_outside", Position.DUMMY)
|
||||
val varInBlock = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
|
||||
val varInBlock = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", emptyList(),null, false, false, false, Position.DUMMY)
|
||||
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
|
||||
|
||||
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
|
||||
|
||||
@@ -35,6 +35,8 @@ internal object DummyMemsizer : IMemSizer {
|
||||
}
|
||||
|
||||
internal object DummyStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -45,6 +47,8 @@ internal object DummyStringEncoder : IStringEncoding {
|
||||
}
|
||||
|
||||
internal object AsciiStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> = str.map { it.code.toUByte() }
|
||||
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||
@@ -55,7 +59,6 @@ internal object AsciiStringEncoder : IStringEncoding {
|
||||
internal object DummyCompilationTarget : ICompilationTarget {
|
||||
override val name: String = "dummy"
|
||||
override val machine: IMachineDefinition = VirtualMachineDefinition() // not really true but I don't want to implement a full dummy machinedef
|
||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
|
||||
@@ -131,7 +131,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
output(" @zp")
|
||||
if(decl.sharedWithAsm)
|
||||
output(" @shared")
|
||||
output(" ${decl.name} ")
|
||||
if(decl.names.size>1)
|
||||
output(decl.names.joinToString(prefix=" "))
|
||||
else
|
||||
output(" ${decl.name} ")
|
||||
if(decl.value!=null) {
|
||||
output("= ")
|
||||
decl.value?.accept(this)
|
||||
@@ -343,6 +346,12 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(chainedAssignment: ChainedAssignment) {
|
||||
chainedAssignment.target.accept(this)
|
||||
output(" = ")
|
||||
chainedAssignment.nested.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||
postIncrDecr.target.accept(this)
|
||||
output(postIncrDecr.operator)
|
||||
|
||||
@@ -8,6 +8,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.SourceCode
|
||||
|
||||
@@ -317,6 +318,14 @@ open class Module(final override var statements: MutableList<Statement>,
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
val textEncoding: Encoding by lazy {
|
||||
val encoding = (statements.singleOrNull { it is Directive && it.directive == "%encoding" } as? Directive)
|
||||
if(encoding!=null)
|
||||
Encoding.entries.first { it.prefix==encoding.args[0].name }
|
||||
else
|
||||
program.encoding.defaultEncoding
|
||||
}
|
||||
|
||||
val isLibrary get() = source.isFromResources
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class Program(val name: String,
|
||||
fun addNewInternedStringvar(string: StringLiteral): Pair<List<String>, VarDecl> {
|
||||
val varName = "string_${internedStringsBlock.statements.size}"
|
||||
val decl = VarDecl(
|
||||
VarDeclType.VAR, VarDeclOrigin.STRINGLITERAL, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
|
||||
VarDeclType.VAR, VarDeclOrigin.STRINGLITERAL, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, emptyList(), string,
|
||||
isArray = false, sharedWithAsm = false, splitArray = false, position = string.position
|
||||
)
|
||||
internedStringsBlock.statements.add(decl)
|
||||
|
||||
@@ -7,7 +7,6 @@ import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.*
|
||||
import prog8.parser.Prog8ANTLRParser
|
||||
import prog8.parser.Prog8ANTLRParser.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
@@ -87,17 +86,11 @@ private fun StatementContext.toAst() : Statement {
|
||||
val vardecl = variabledeclaration()?.toAst()
|
||||
if(vardecl!=null) return vardecl
|
||||
|
||||
assignment()?.let {
|
||||
return Assignment(it.assign_target().toAst(), it.expression().toAst(), AssignmentOrigin.USERCODE, it.toPosition())
|
||||
}
|
||||
val assignment = assignment()?.toAst()
|
||||
if(assignment!=null) return assignment
|
||||
|
||||
augassignment()?.let {
|
||||
// replace A += X with A = A + X
|
||||
val target = it.assign_target().toAst()
|
||||
val oper = it.operator.text.substringBefore('=')
|
||||
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
|
||||
return Assignment(it.assign_target().toAst(), expression, AssignmentOrigin.USERCODE, it.toPosition())
|
||||
}
|
||||
val augassign = augassignment()?.toAst()
|
||||
if(augassign!=null) return augassign
|
||||
|
||||
postincrdecr()?.let {
|
||||
return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition())
|
||||
@@ -145,11 +138,14 @@ private fun StatementContext.toAst() : Statement {
|
||||
val repeatloop = repeatloop()?.toAst()
|
||||
if(repeatloop!=null) return repeatloop
|
||||
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
if(whenstmt!=null) return whenstmt
|
||||
|
||||
val breakstmt = breakstmt()?.toAst()
|
||||
if(breakstmt!=null) return breakstmt
|
||||
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
if(whenstmt!=null) return whenstmt
|
||||
val continuestmt = continuestmt()?.toAst()
|
||||
if(continuestmt!=null) return continuestmt
|
||||
|
||||
val unrollstmt = unrollloop()?.toAst()
|
||||
if(unrollstmt!=null) return unrollstmt
|
||||
@@ -241,7 +237,10 @@ private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||
}
|
||||
}
|
||||
}
|
||||
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
|
||||
val identifiers = vardecl.identifier()
|
||||
if(identifiers.size>1)
|
||||
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
|
||||
AsmSubroutineParameter(identifiers[0].NAME().text, datatype, registerorpair, statusregister, toPosition())
|
||||
}
|
||||
|
||||
private fun Functioncall_stmtContext.toAst(): Statement {
|
||||
@@ -307,7 +306,11 @@ private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
var datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
|
||||
if(it.ARRAYSIG()!=null || it.arrayindex()!=null)
|
||||
datatype = ElementToArrayTypes.getValue(datatype)
|
||||
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
||||
|
||||
val identifiers = it.identifier()
|
||||
if(identifiers.size>1)
|
||||
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
|
||||
SubroutineParameter(identifiers[0].NAME().text, datatype, it.toPosition())
|
||||
}
|
||||
|
||||
private fun Assign_targetContext.toAst() : AssignTarget {
|
||||
@@ -336,6 +339,22 @@ private fun ClobberContext.toAst() : Set<CpuRegister> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun AssignmentContext.toAst(): Statement {
|
||||
val nestedAssign = assignment()
|
||||
if(nestedAssign==null)
|
||||
return Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
|
||||
else
|
||||
return ChainedAssignment(assign_target().toAst(), nestedAssign.toAst(), toPosition())
|
||||
}
|
||||
|
||||
private fun AugassignmentContext.toAst(): Assignment {
|
||||
// replace A += X with A = A + X
|
||||
val target = assign_target().toAst()
|
||||
val oper = operator.text.substringBefore('=')
|
||||
val expression = BinaryExpression(target.toExpression(), oper, expression().toAst(), expression().toPosition())
|
||||
return Assignment(assign_target().toAst(), expression, AssignmentOrigin.USERCODE, toPosition())
|
||||
}
|
||||
|
||||
private fun DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
|
||||
|
||||
private fun ArrayindexContext.toAst() : ArrayIndex =
|
||||
@@ -352,13 +371,14 @@ private fun DirectiveargContext.toAst() : DirectiveArg {
|
||||
}
|
||||
|
||||
private fun IntegerliteralContext.toAst(): NumericLiteralNode {
|
||||
fun makeLiteral(text: String, radix: Int): NumericLiteralNode {
|
||||
fun makeLiteral(literalTextWithGrouping: String, radix: Int): NumericLiteralNode {
|
||||
val literalText = literalTextWithGrouping.replace("_", "")
|
||||
val integer: Int
|
||||
var datatype = DataType.UBYTE
|
||||
when (radix) {
|
||||
10 -> {
|
||||
integer = try {
|
||||
text.toInt()
|
||||
literalText.toInt()
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid decimal literal ${x.message}", toPosition())
|
||||
}
|
||||
@@ -367,23 +387,24 @@ private fun IntegerliteralContext.toAst(): NumericLiteralNode {
|
||||
in -128..127 -> DataType.BYTE
|
||||
in 0..65535 -> DataType.UWORD
|
||||
in -32768..32767 -> DataType.WORD
|
||||
in -2147483647..2147483647 -> DataType.LONG
|
||||
else -> DataType.FLOAT
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
if(text.length>8)
|
||||
if(literalText.length>8)
|
||||
datatype = DataType.UWORD
|
||||
try {
|
||||
integer = text.toInt(2)
|
||||
integer = literalText.toInt(2)
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid binary literal ${x.message}", toPosition())
|
||||
}
|
||||
}
|
||||
16 -> {
|
||||
if(text.length>2)
|
||||
if(literalText.length>2)
|
||||
datatype = DataType.UWORD
|
||||
try {
|
||||
integer = text.toInt(16)
|
||||
integer = literalText.toInt(16)
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid hexadecimal literal ${x.message}", toPosition())
|
||||
}
|
||||
@@ -418,6 +439,7 @@ private fun ExpressionContext.toAst() : Expression {
|
||||
DataType.BYTE -> NumericLiteral(DataType.BYTE, intLit.number, litval.toPosition())
|
||||
DataType.UWORD -> NumericLiteral(DataType.UWORD, intLit.number, litval.toPosition())
|
||||
DataType.WORD -> NumericLiteral(DataType.WORD, intLit.number, litval.toPosition())
|
||||
DataType.LONG -> NumericLiteral(DataType.LONG, intLit.number, litval.toPosition())
|
||||
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, intLit.number, litval.toPosition())
|
||||
else -> throw FatalAstException("invalid datatype for numeric literal")
|
||||
}
|
||||
@@ -521,7 +543,7 @@ private fun Expression_listContext.toAst() = expression().map{ it.toAst() }
|
||||
private fun Scoped_identifierContext.toAst() : IdentifierReference =
|
||||
IdentifierReference(NAME().map { it.text }, toPosition())
|
||||
|
||||
private fun FloatliteralContext.toAst() = text.toDouble()
|
||||
private fun FloatliteralContext.toAst() = text.replace("_","").toDouble()
|
||||
|
||||
private fun BooleanliteralContext.toAst() = when(text) {
|
||||
"true" -> true
|
||||
@@ -573,6 +595,8 @@ private fun ForloopContext.toAst(): ForLoop {
|
||||
|
||||
private fun BreakstmtContext.toAst() = Break(toPosition())
|
||||
|
||||
private fun ContinuestmtContext.toAst() = Continue(toPosition())
|
||||
|
||||
private fun WhileloopContext.toAst(): WhileLoop {
|
||||
val condition = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
@@ -628,12 +652,16 @@ private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl
|
||||
options.ZEROPAGE().isNotEmpty() -> ZeropageWish.PREFER_ZEROPAGE
|
||||
else -> ZeropageWish.DONTCARE
|
||||
}
|
||||
val identifiers = identifier()
|
||||
val name = if(identifiers.size==1) identifiers[0].NAME().text else "<multiple>"
|
||||
|
||||
return VarDecl(
|
||||
type, VarDeclOrigin.USERCODE,
|
||||
datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||
zp,
|
||||
arrayindex()?.toAst(),
|
||||
varname.text,
|
||||
name,
|
||||
if(identifiers.size==1) emptyList() else identifiers.map { it.NAME().text },
|
||||
value,
|
||||
ARRAYSIG() != null || arrayindex() != null,
|
||||
options.SHARED().isNotEmpty(),
|
||||
|
||||
@@ -472,6 +472,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
in -128..127 -> NumericLiteral(DataType.BYTE, dvalue, position)
|
||||
in 0..65535 -> NumericLiteral(DataType.UWORD, dvalue, position)
|
||||
in -32768..32767 -> NumericLiteral(DataType.WORD, dvalue, position)
|
||||
in -2147483647..2147483647 -> NumericLiteral(DataType.LONG, dvalue, position)
|
||||
else -> NumericLiteral(DataType.FLOAT, dvalue, position)
|
||||
}
|
||||
}
|
||||
@@ -484,6 +485,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
in -128..127 -> NumericLiteral(DataType.BYTE, dvalue, position)
|
||||
in 0..65535 -> NumericLiteral(DataType.UWORD, dvalue, position)
|
||||
in -32768..32767 -> NumericLiteral(DataType.WORD, dvalue, position)
|
||||
in -2147483647..2147483647 -> NumericLiteral(DataType.LONG, dvalue, position)
|
||||
else -> throw FatalAstException("integer overflow: $dvalue")
|
||||
}
|
||||
}
|
||||
@@ -492,6 +494,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
return when (value) {
|
||||
in 0u..255u -> NumericLiteral(DataType.UBYTE, value.toDouble(), position)
|
||||
in 0u..65535u -> NumericLiteral(DataType.UWORD, value.toDouble(), position)
|
||||
in 0u..2147483647u -> NumericLiteral(DataType.LONG, value.toDouble(), position)
|
||||
else -> throw FatalAstException("unsigned integer overflow: $value")
|
||||
}
|
||||
}
|
||||
@@ -561,6 +564,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.LONG)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
}
|
||||
@@ -583,6 +588,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
if(targettype==DataType.LONG)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(targettype== DataType.BYTE && number <= 127)
|
||||
@@ -595,6 +602,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
if(targettype==DataType.LONG)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(targettype== DataType.BYTE && number >= -128 && number <=127)
|
||||
@@ -615,6 +624,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
if(targettype==DataType.LONG)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
try {
|
||||
@@ -626,6 +637,8 @@ 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.LONG && number >=0 && number <= 2147483647)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if(targettype==DataType.BOOL)
|
||||
return CastValue(true, fromBoolean(number!=0.0, position))
|
||||
} catch (x: ExpressionError) {
|
||||
@@ -635,6 +648,24 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
DataType.BOOL -> {
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
}
|
||||
DataType.LONG -> {
|
||||
try {
|
||||
if (targettype == DataType.BYTE && number >= -128 && number <= 127)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if (targettype == DataType.UBYTE && number >= 0 && number <= 255)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
if (targettype == DataType.WORD && number >= -32768 && number <= 32767)
|
||||
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))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return CastValue(true, NumericLiteral(targettype, number, position))
|
||||
} catch (x: ExpressionError) {
|
||||
return CastValue(false, null)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("type cast of weird type $type")
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ object InferredTypes {
|
||||
DataType.BYTE to InferredType.known(DataType.BYTE),
|
||||
DataType.UWORD to InferredType.known(DataType.UWORD),
|
||||
DataType.WORD to InferredType.known(DataType.WORD),
|
||||
DataType.LONG to InferredType.known(DataType.LONG),
|
||||
DataType.FLOAT to InferredType.known(DataType.FLOAT),
|
||||
DataType.BOOL to InferredType.known(DataType.BOOL),
|
||||
DataType.STR to InferredType.known(DataType.STR),
|
||||
|
||||
@@ -173,6 +173,20 @@ class Break(override val position: Position) : Statement() {
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class Continue(override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = false
|
||||
override fun copy() = Break(position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
|
||||
enum class VarDeclOrigin {
|
||||
USERCODE,
|
||||
@@ -194,6 +208,7 @@ class VarDecl(val type: VarDeclType,
|
||||
var zeropage: ZeropageWish,
|
||||
var arraysize: ArrayIndex?,
|
||||
override val name: String,
|
||||
val names: List<String>,
|
||||
var value: Expression?,
|
||||
val isArray: Boolean,
|
||||
val sharedWithAsm: Boolean,
|
||||
@@ -208,7 +223,7 @@ class VarDecl(val type: VarDeclType,
|
||||
private var autoHeapValueSequenceNumber = 0
|
||||
|
||||
fun fromParameter(param: SubroutineParameter): VarDecl {
|
||||
return VarDecl(VarDeclType.VAR, VarDeclOrigin.SUBROUTINEPARAM, param.type, ZeropageWish.DONTCARE, null, param.name, null,
|
||||
return VarDecl(VarDeclType.VAR, VarDeclOrigin.SUBROUTINEPARAM, param.type, ZeropageWish.DONTCARE, null, param.name, emptyList(), null,
|
||||
isArray = false,
|
||||
sharedWithAsm = false,
|
||||
splitArray = false,
|
||||
@@ -221,7 +236,7 @@ class VarDecl(val type: VarDeclType,
|
||||
val arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") }
|
||||
val declaredType = ArrayToElementTypes.getValue(arrayDt)
|
||||
val arraysize = ArrayIndex.forArray(array)
|
||||
return VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array,
|
||||
return VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, emptyList(), array,
|
||||
isArray = true, sharedWithAsm = false, splitArray = splitArray, position = array.position)
|
||||
}
|
||||
}
|
||||
@@ -283,7 +298,9 @@ class VarDecl(val type: VarDeclType,
|
||||
}
|
||||
|
||||
override fun copy(): VarDecl {
|
||||
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), name, value?.copy(),
|
||||
if(names.size>1)
|
||||
throw FatalAstException("should not copy a vardecl that still has multiple names")
|
||||
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), name, names, value?.copy(),
|
||||
isArray, sharedWithAsm, splitArray, position)
|
||||
copy.allowInitializeWithZero = this.allowInitializeWithZero
|
||||
return copy
|
||||
@@ -298,6 +315,36 @@ class VarDecl(val type: VarDeclType,
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean =
|
||||
value?.referencesIdentifier(nameInSource)==true ||
|
||||
this.arraysize?.referencesIdentifier(nameInSource)==true
|
||||
|
||||
fun desugarMultiDecl(): List<VarDecl> {
|
||||
if(value?.isSimple==true) {
|
||||
// just copy the initialization value to a separata vardecl for each component
|
||||
return names.map {
|
||||
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), it, emptyList(), value?.copy(),
|
||||
isArray, sharedWithAsm, splitArray, position)
|
||||
copy.allowInitializeWithZero = this.allowInitializeWithZero
|
||||
copy
|
||||
}
|
||||
} else {
|
||||
// evaluate the value once in the vardecl for the first component, and set the other components to the first
|
||||
val first = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), names[0], emptyList(), value?.copy(),
|
||||
isArray, sharedWithAsm, splitArray, position)
|
||||
first.allowInitializeWithZero = this.allowInitializeWithZero
|
||||
val firstVar = firstVarAsValue(first)
|
||||
return listOf(first) + names.drop(1 ).map {
|
||||
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), it, emptyList(), firstVar.copy(),
|
||||
isArray, sharedWithAsm, splitArray, position)
|
||||
copy.allowInitializeWithZero = this.allowInitializeWithZero
|
||||
copy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun firstVarAsValue(first: VarDecl): Expression {
|
||||
if(first.isArray || first.type!=VarDeclType.VAR)
|
||||
throw FatalAstException("invalid multi decl type $first")
|
||||
return IdentifierReference(listOf(first.name), first.position)
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayIndex(var indexExpr: Expression,
|
||||
@@ -336,8 +383,34 @@ class ArrayIndex(var indexExpr: Expression,
|
||||
enum class AssignmentOrigin {
|
||||
USERCODE,
|
||||
VARINIT,
|
||||
OPTIMIZER,
|
||||
ASMGEN
|
||||
OPTIMIZER
|
||||
}
|
||||
|
||||
|
||||
class ChainedAssignment(var target: AssignTarget, var nested: Statement, override val position: Position): Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
target.linkParents(this)
|
||||
nested.linkParents(this)
|
||||
}
|
||||
|
||||
override fun copy() = throw FatalAstException("you're not supposed to copy a ChainedAssignment")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===target -> target = replacement as AssignTarget
|
||||
node===nested -> nested = replacement as Statement
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun toString() = "ChainedAssignment(target: $target, nested: $nested, pos=$position)"
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = target.referencesIdentifier(nameInSource) || nested.referencesIdentifier(nameInSource)
|
||||
}
|
||||
|
||||
class Assignment(var target: AssignTarget, var value: Expression, var origin: AssignmentOrigin, override val position: Position) : Statement() {
|
||||
@@ -345,7 +418,7 @@ class Assignment(var target: AssignTarget, var value: Expression, var origin: As
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
this.target.linkParents(this)
|
||||
target.linkParents(this)
|
||||
value.linkParents(this)
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 149 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user