mirror of
https://github.com/irmen/prog8.git
synced 2026-10-23 12:02:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa949165c7 | ||
|
|
d22359b6e7 | ||
|
|
d73709653d | ||
|
|
405926e811 | ||
|
|
36758f41a4 | ||
|
|
7ebc9c79cf | ||
|
|
e0668b55b9 | ||
|
|
76c09da961 | ||
|
|
7e3b8c2c59 | ||
|
|
ecca854c7c | ||
|
|
3b0d7ea960 | ||
|
|
f70fa42eac | ||
|
|
5698de6cf4 | ||
|
|
c5a333a904 | ||
|
|
ff324955dd | ||
|
|
70436f5dca | ||
|
|
31177a2b1b | ||
|
|
4de012fc49 | ||
|
|
ee2888e744 | ||
|
|
efe4df92dc | ||
|
|
723ab54f97 | ||
|
|
d9389afc66 | ||
|
|
e7178ee496 | ||
|
|
d5f35bb3fb | ||
|
|
72f1a779f2 | ||
|
|
3277544295 | ||
|
|
98d2c64d5d | ||
|
|
f68b46fc60 | ||
|
|
d54ab856e7 | ||
|
|
16b24fadea | ||
|
|
b3803cbdf1 | ||
|
|
2ceaa25181 | ||
|
|
513611c5a6 | ||
|
|
7ec4ba40ad | ||
|
|
92374e122b | ||
|
|
94f12732ab | ||
|
|
0904712a00 | ||
|
|
32becdbced | ||
|
|
34aa21f7d9 | ||
|
|
cc81dd7d3e | ||
|
|
335213b55f | ||
|
|
13ab4166c0 | ||
|
|
3dc5a0e7f8 | ||
|
|
e15c5cde53 | ||
|
|
d88c09b098 | ||
|
|
893b383bdf | ||
|
|
dd7c9d62e6 | ||
|
|
97c5c90eff | ||
|
|
1fb94e7a7b | ||
|
|
daca87c6d0 | ||
|
|
203ec5fa46 | ||
|
|
9ea69c07b8 | ||
|
|
68539d6cc9 | ||
|
|
f75fd0811e | ||
|
|
836bc9d456 | ||
|
|
a37769aafe | ||
|
|
68e62e4bd2 | ||
|
|
a5cd3728c9 | ||
|
|
a48ce35f0b | ||
|
|
e1835b5775 | ||
|
|
433832b329 | ||
|
|
ee81da14d6 | ||
|
|
6395d1908e | ||
|
|
989a5a2f8a | ||
|
|
b7a622c68e | ||
|
|
a8507b437d | ||
|
|
e505bf9ccf | ||
|
|
a289b32053 | ||
|
|
c3f1f09ad1 | ||
|
|
70ee2026ff | ||
|
|
690782bf60 | ||
|
|
755cc4835e | ||
|
|
a684ea46e4 | ||
|
|
8fbe13f99d | ||
|
|
452e9e275f | ||
|
|
cd40088636 | ||
|
|
9b9e6f4af5 | ||
|
|
ae6eeadf54 | ||
|
|
5268b05060 | ||
|
|
390263a34e | ||
|
|
55646edc3e | ||
|
|
8d177beb78 | ||
|
|
1da0c59182 | ||
|
|
36e8f10d2b | ||
|
|
cdf5a8f20f | ||
|
|
eb64d92333 | ||
|
|
eb55da63ef | ||
|
|
918302f79b | ||
|
|
9d7131d9f6 | ||
|
|
229c1114dd | ||
|
|
885df9156f | ||
|
|
c319233ddc | ||
|
|
958b5c0780 | ||
|
|
880c0a5da8 | ||
|
|
237c6dc856 | ||
|
|
ccf6e32bf9 | ||
|
|
a1874f6f00 | ||
|
|
95e4490a8a | ||
|
|
31c132c2eb | ||
|
|
00b0ec58b4 | ||
|
|
a1d0e5bb65 | ||
|
|
03e0d4b2e8 | ||
|
|
6afdd4e6fd |
No files matched your search
Generated
+1
-1
@@ -4,6 +4,6 @@
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.0-release-358" />
|
||||
<option name="version" value="1.9.20" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.12.0">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.1">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.12.0/antlr4-4.12.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.12.0/antlr4-runtime-4.12.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
|
||||
+6
-6
@@ -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.6.2" />
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.7.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.6.2/kotest-assertions-core-jvm-5.6.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$/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.6.2/kotest-assertions-shared-jvm-5.6.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/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/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.6.2/kotest-common-jvm-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.6.2/kotest-assertions-api-jvm-5.6.2.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$/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>
|
||||
|
||||
+13
-13
@@ -1,29 +1,29 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.6.2" />
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.7.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.6.2/kotest-runner-junit5-jvm-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.6.2/kotest-framework-api-jvm-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.6.2/kotest-assertions-shared-jvm-5.6.2.jar!/" />
|
||||
<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/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.6.2/kotest-common-jvm-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.6.2/kotest-framework-engine-jvm-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.157/classgraph-4.8.157.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/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!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.7.0/kotlinx-coroutines-debug-1.7.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.6.2/kotest-framework-discovery-jvm-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.6.2/kotest-assertions-core-jvm-5.6.2.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$/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.6.2/kotest-assertions-api-jvm-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.6.2/kotest-extensions-jvm-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.6.2/kotest-framework-concurrency-jvm-5.6.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-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$/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!/" />
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.5" />
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.5/kotlinx-cli-jvm-0.3.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
Generated
+3
-3
@@ -1,9 +1,9 @@
|
||||
<component name="libraryTable">
|
||||
<library name="slf4j.simple" type="repository">
|
||||
<properties maven-id="org.slf4j:slf4j-simple:2.0.7" />
|
||||
<properties maven-id="org.slf4j:slf4j-simple:2.0.9" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.7/slf4j-simple-2.0.7.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.9/slf4j-simple-2.0.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
Generated
+3
@@ -16,6 +16,9 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11" />
|
||||
</component>
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
|
||||
@@ -32,10 +32,10 @@ dependencies {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,13 +196,10 @@ class StStaticVariable(name: String,
|
||||
}
|
||||
if(onetimeInitializationNumericValue!=null) {
|
||||
require(dt in NumericDatatypes)
|
||||
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
|
||||
}
|
||||
if(onetimeInitializationArrayValue!=null) {
|
||||
require(dt in ArrayDatatypes)
|
||||
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
|
||||
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
|
||||
}
|
||||
require(length==onetimeInitializationArrayValue.size)
|
||||
}
|
||||
if(onetimeInitializationStringValue!=null) {
|
||||
require(dt == DataType.STR)
|
||||
|
||||
@@ -74,10 +74,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
numElements = value.value.length + 1 // include the terminating 0-byte
|
||||
}
|
||||
is PtArray -> {
|
||||
val array = makeInitialArray(value)
|
||||
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
|
||||
initialArray = makeInitialArray(value)
|
||||
initialString = null
|
||||
numElements = array.size
|
||||
numElements = initialArray.size
|
||||
require(node.arraySize?.toInt()==numElements)
|
||||
}
|
||||
else -> {
|
||||
@@ -126,7 +125,11 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
|
||||
return value.children.map {
|
||||
when(it) {
|
||||
is PtAddressOf -> StArrayElement(null, it.identifier.name)
|
||||
is PtAddressOf -> {
|
||||
if(it.isFromArrayElement)
|
||||
TODO("address-of array element $it in initial array value")
|
||||
StArrayElement(null, it.identifier.name)
|
||||
}
|
||||
is PtIdentifier -> StArrayElement(null, it.name)
|
||||
is PtNumber -> StArrayElement(it.number, null)
|
||||
else -> throw AssemblyError("invalid array element $it")
|
||||
|
||||
@@ -75,6 +75,7 @@ class PtBlock(name: String,
|
||||
val library: Boolean,
|
||||
val forceOutput: Boolean,
|
||||
val noSymbolPrefixing: Boolean,
|
||||
val veraFxMuls: Boolean,
|
||||
val alignment: BlockAlignment,
|
||||
val source: SourceCode, // taken from the module the block is defined in.
|
||||
position: Position
|
||||
|
||||
@@ -24,7 +24,17 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
|
||||
infix fun isSameAs(other: PtExpression): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
|
||||
is PtAddressOf -> {
|
||||
if(other !is PtAddressOf)
|
||||
return false
|
||||
if (other.type!==type || !(other.identifier isSameAs identifier))
|
||||
return false
|
||||
if(other.children.size!=children.size)
|
||||
return false
|
||||
if(children.size==1)
|
||||
return true
|
||||
return arrayIndexExpr!! isSameAs other.arrayIndexExpr!!
|
||||
}
|
||||
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index && other.splitWords==splitWords
|
||||
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
|
||||
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
||||
@@ -105,7 +115,12 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
|
||||
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
||||
val identifier: PtIdentifier
|
||||
get() = children.single() as PtIdentifier
|
||||
get() = children[0] as PtIdentifier
|
||||
val arrayIndexExpr: PtExpression?
|
||||
get() = if(children.size==2) children[1] as PtExpression else null
|
||||
|
||||
val isFromArrayElement: Boolean
|
||||
get() = children.size==2
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
|
||||
val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
// this set of function have no return value and operate in-place:
|
||||
"setlsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
|
||||
"setmsb" to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null),
|
||||
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
@@ -119,8 +121,8 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
"pokemon" 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),
|
||||
"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),
|
||||
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
|
||||
@@ -131,4 +133,4 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
)
|
||||
|
||||
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")
|
||||
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
|
||||
@@ -8,6 +8,7 @@ class CompilationOptions(val output: OutputType,
|
||||
val launcher: CbmPrgLauncherType,
|
||||
val zeropage: ZeropageType,
|
||||
val zpReserved: List<UIntRange>,
|
||||
val zpAllowed: List<UIntRange>,
|
||||
val floats: Boolean,
|
||||
val noSysInit: Boolean,
|
||||
val compTarget: ICompilationTarget,
|
||||
@@ -21,10 +22,15 @@ class CompilationOptions(val output: OutputType,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var varsHighBank: Int? = null,
|
||||
var splitWordArrays: Boolean = false,
|
||||
var breakpointCpuInstruction: Boolean = false,
|
||||
var outputDir: Path = Path(""),
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
) {
|
||||
init {
|
||||
compTarget.machine.initializeMemoryAreas(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,13 @@ enum class RegisterOrPair {
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
|
||||
return when(cpu) {
|
||||
CpuRegister.A -> A
|
||||
CpuRegister.X -> X
|
||||
CpuRegister.Y -> Y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun asCpuRegister(): CpuRegister = when(this) {
|
||||
@@ -152,6 +159,7 @@ val ElementToArrayTypes = mapOf(
|
||||
DataType.FLOAT to DataType.ARRAY_F,
|
||||
DataType.BOOL to DataType.ARRAY_BOOL
|
||||
)
|
||||
|
||||
val Cx16VirtualRegisters = arrayOf(
|
||||
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
||||
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
|
||||
@@ -159,6 +167,10 @@ val Cx16VirtualRegisters = arrayOf(
|
||||
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
|
||||
)
|
||||
|
||||
val CpuRegisters = setOf(
|
||||
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
|
||||
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
|
||||
)
|
||||
|
||||
|
||||
enum class OutputType {
|
||||
|
||||
@@ -42,6 +42,13 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
}
|
||||
}
|
||||
|
||||
fun retainAllowed() {
|
||||
synchronized(this) {
|
||||
for(allowed in options.zpAllowed)
|
||||
free.retainAll { it in allowed }
|
||||
}
|
||||
}
|
||||
|
||||
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
|
||||
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
|
||||
fun hasWordAvailable(): Boolean {
|
||||
|
||||
@@ -48,6 +48,7 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
|
||||
@@ -69,6 +69,7 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
|
||||
@@ -53,11 +53,11 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||
if(options.zeropage != ZeropageType.DONTUSE) {
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
@@ -75,6 +75,8 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
|
||||
@@ -24,7 +24,7 @@ object PetsciiEncoding {
|
||||
'\ufffe', // 0x0A -> UNDEFINED
|
||||
'\ufffe', // 0x0B -> UNDEFINED
|
||||
'\ufffe', // 0x0C -> UNDEFINED
|
||||
'\r' , // 0x0D -> CARRIAGE RETURN
|
||||
'\n' , // 0x0D -> LINE FEED (RETURN)
|
||||
'\u000e', // 0x0E -> SHIFT OUT
|
||||
'\ufffe', // 0x0F -> UNDEFINED
|
||||
'\ufffe', // 0x10 -> UNDEFINED
|
||||
@@ -152,7 +152,7 @@ object PetsciiEncoding {
|
||||
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
|
||||
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
|
||||
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
|
||||
'\n' , // 0x8D -> LINE FEED
|
||||
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
|
||||
'\u000f', // 0x8E -> SHIFT IN
|
||||
'\ufffe', // 0x8F -> UNDEFINED
|
||||
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
|
||||
@@ -283,7 +283,7 @@ object PetsciiEncoding {
|
||||
'\ufffe', // 0x0A -> UNDEFINED
|
||||
'\ufffe', // 0x0B -> UNDEFINED
|
||||
'\ufffe', // 0x0C -> UNDEFINED
|
||||
'\r' , // 0x0D -> CARRIAGE RETURN
|
||||
'\n' , // 0x0D -> LINE FEED (RETURN)
|
||||
'\u000e', // 0x0E -> SHIFT OUT
|
||||
'\ufffe', // 0x0F -> UNDEFINED
|
||||
'\ufffe', // 0x10 -> UNDEFINED
|
||||
@@ -411,7 +411,7 @@ object PetsciiEncoding {
|
||||
'\uf113', // 0x8A -> FUNCTION KEY 4 (CUS)
|
||||
'\uf115', // 0x8B -> FUNCTION KEY 6 (CUS)
|
||||
'\uf117', // 0x8C -> FUNCTION KEY 8 (CUS)
|
||||
'\n' , // 0x8D -> LINE FEED
|
||||
'\r' , // 0x8D -> CARRIAGE RETURN (SHIFT-RETURN)
|
||||
'\u000f', // 0x8E -> SHIFT IN
|
||||
'\ufffe', // 0x8F -> UNDEFINED
|
||||
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
|
||||
@@ -1061,6 +1061,7 @@ object PetsciiEncoding {
|
||||
'}' -> '├'
|
||||
'|' -> '│'
|
||||
'\\' -> '╲'
|
||||
'\r' -> '\n' // to make \r (carriage returrn) equivalent to \n (line feed): RETURN ($0d)
|
||||
else -> chr
|
||||
}
|
||||
|
||||
@@ -1076,7 +1077,10 @@ object PetsciiEncoding {
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Petscii character for '${chr}' (${chr.code})")
|
||||
if(chr.isISOControl())
|
||||
throw CharConversionException("no ${case}Petscii character for char #${chr.code}")
|
||||
else
|
||||
throw CharConversionException("no ${case}Petscii character for char #${chr.code} '${chr}'")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1119,7 +1123,10 @@ object PetsciiEncoding {
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '${chr}' (${chr.code})")
|
||||
if(chr.isISOControl())
|
||||
throw CharConversionException("no ${case}Screencode character for char #${chr.code}")
|
||||
else
|
||||
throw CharConversionException("no ${case}Screencode character for char #${chr.code} '${chr}'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
|
||||
allocateCx16VirtualRegisters()
|
||||
retainAllowed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
|
||||
@@ -30,16 +30,16 @@ 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.6.2'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
test {
|
||||
|
||||
@@ -503,24 +503,18 @@ class AsmGen6502Internal (
|
||||
return
|
||||
}
|
||||
|
||||
val indexVar = expr.index as? PtIdentifier
|
||||
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.index.position}")
|
||||
|
||||
val indexName = asmVariableName(indexVar)
|
||||
|
||||
if(expr.splitWords) {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" lda $indexName")
|
||||
CpuRegister.X -> out(" ldx $indexName")
|
||||
CpuRegister.Y -> out(" ldy $indexName")
|
||||
}
|
||||
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
|
||||
return
|
||||
}
|
||||
|
||||
when (elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
|
||||
out(" asl a")
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
@@ -529,12 +523,13 @@ class AsmGen6502Internal (
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
|
||||
out("""
|
||||
lda $indexName
|
||||
sta P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $indexName"""
|
||||
adc P8ZP_SCRATCH_REG"""
|
||||
)
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
@@ -568,7 +563,7 @@ class AsmGen6502Internal (
|
||||
when(reg) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes)
|
||||
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes, true)
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY,
|
||||
@@ -582,6 +577,34 @@ class AsmGen6502Internal (
|
||||
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
if (value.asConstInteger()==0) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if (isTargetCpu(CpuType.CPU6502))
|
||||
out("lda #0 | sta ${target.asmVarname}")
|
||||
else
|
||||
out("stz ${target.asmVarname}")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val address = target.memory!!.address.asConstInteger()
|
||||
if(address!=null) {
|
||||
if (isTargetCpu(CpuType.CPU6502))
|
||||
out("lda #0 | sta ${address.toHex()}")
|
||||
else
|
||||
out(" stz ${address.toHex()}")
|
||||
return
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
val zero = PtNumber(DataType.UBYTE, 0.0, value.position)
|
||||
zero.parent = value
|
||||
assignExpressionToRegister(zero, target.register!!, false)
|
||||
return
|
||||
}
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
assignRegister(RegisterOrPair.A, target)
|
||||
}
|
||||
@@ -1026,6 +1049,15 @@ $repeatLabel""")
|
||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||
breakpointLabels.add(label)
|
||||
out(label)
|
||||
if(options.breakpointCpuInstruction) {
|
||||
val instruction =
|
||||
when(options.compTarget.machine.cpu) {
|
||||
CpuType.CPU6502 -> "brk"
|
||||
CpuType.CPU65c02 -> "stp"
|
||||
else -> throw AssemblyError("invalid cpu type")
|
||||
}
|
||||
out(" $instruction")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendAYlsb(valueDt: DataType) {
|
||||
@@ -2417,8 +2449,7 @@ $repeatLabel""")
|
||||
cmp #<$number
|
||||
bne $jumpIfFalseLabel
|
||||
cpy #>$number
|
||||
bne $jumpIfFalseLabel
|
||||
""")
|
||||
bne $jumpIfFalseLabel""")
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
@@ -2426,18 +2457,21 @@ $repeatLabel""")
|
||||
cmp ${asmVariableName(right)}
|
||||
bne $jumpIfFalseLabel
|
||||
cpy ${asmVariableName(right)}+1
|
||||
bne $jumpIfFalseLabel
|
||||
""")
|
||||
bne $jumpIfFalseLabel""")
|
||||
}
|
||||
is PtAddressOf -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
val name = asmSymbolName(right.identifier)
|
||||
out("""
|
||||
cmp #<$name
|
||||
bne $jumpIfFalseLabel
|
||||
cpy #>$name
|
||||
bne $jumpIfFalseLabel
|
||||
""")
|
||||
if(right.isFromArrayElement) {
|
||||
TODO("address-of array element $name at ${right.position}")
|
||||
// assignmentAsmGen.assignAddressOf(target, name, right.arrayIndexExpr)
|
||||
} else {
|
||||
out("""
|
||||
cmp #<$name
|
||||
bne $jumpIfFalseLabel
|
||||
cpy #>$name
|
||||
bne $jumpIfFalseLabel""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if(left.isSimple()) {
|
||||
@@ -2518,12 +2552,16 @@ $repeatLabel""")
|
||||
is PtAddressOf -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
val name = asmSymbolName(right.identifier)
|
||||
out("""
|
||||
cmp #<$name
|
||||
bne +
|
||||
cpy #>$name
|
||||
beq $jumpIfFalseLabel
|
||||
+""")
|
||||
if(right.isFromArrayElement) {
|
||||
TODO("address-of array element $name at ${right.position}")
|
||||
} else {
|
||||
out("""
|
||||
cmp #<$name
|
||||
bne +
|
||||
cpy #>$name
|
||||
beq $jumpIfFalseLabel
|
||||
+""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if(left.isSimple()) {
|
||||
@@ -2849,8 +2887,12 @@ $repeatLabel""")
|
||||
is PtAddressOf -> {
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
val name = asmSymbolName(left.identifier)
|
||||
code("#>$name", "#<$name")
|
||||
return true
|
||||
if(left.isFromArrayElement) {
|
||||
TODO("address-of array element $name at ${left.position}")
|
||||
} else {
|
||||
code("#>$name", "#<$name")
|
||||
return true
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY)
|
||||
@@ -2898,8 +2940,12 @@ $repeatLabel""")
|
||||
is PtAddressOf -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
val name = asmSymbolName(right.identifier)
|
||||
code("#>$name", "#<$name")
|
||||
return true
|
||||
if(right.isFromArrayElement) {
|
||||
TODO("address-of array element $name at ${right.position}")
|
||||
} else {
|
||||
code("#>$name", "#<$name")
|
||||
return true
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.core.Cx16VirtualRegisters
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.core.RegisterOrStatusflag
|
||||
|
||||
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
@@ -11,50 +10,19 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
// order is:
|
||||
// 1) cx16 virtual word registers,
|
||||
// 2) paired CPU registers,
|
||||
// 3) single CPU registers (X last), except A,
|
||||
// 3) single CPU registers (order Y,X,A),
|
||||
// 4) CPU Carry status flag
|
||||
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
|
||||
val args = sub.parameters.withIndex()
|
||||
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
|
||||
val (regsWithoutA, args4) = args3.partition { it.value.first.registerOrPair != RegisterOrPair.A }
|
||||
val (regA, rest) = args4.partition { it.value.first.registerOrPair != null }
|
||||
val (singleRegs, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
regsWithoutA.forEach {
|
||||
if(it.value.first.registerOrPair != RegisterOrPair.X)
|
||||
order += it.index
|
||||
}
|
||||
regsWithoutA.firstOrNull { it.value.first.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
|
||||
singleRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
|
||||
rest.forEach { order += it.index }
|
||||
regA.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
return order
|
||||
}
|
||||
|
||||
fun asmsub6502ArgsHaveRegisterClobberRisk(
|
||||
args: List<PtExpression>,
|
||||
params: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>
|
||||
): Boolean {
|
||||
fun isClobberRisk(expr: PtExpression): Boolean {
|
||||
when (expr) {
|
||||
is PtArrayIndexer -> {
|
||||
return params.any {
|
||||
it.first.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
}
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if (expr.name == "lsb" || expr.name == "msb")
|
||||
return isClobberRisk(expr.args[0])
|
||||
if (expr.name == "mkword")
|
||||
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
|
||||
return !expr.isSimple()
|
||||
}
|
||||
else -> return !expr.isSimple()
|
||||
}
|
||||
}
|
||||
|
||||
return args.size>1 && args.any { isClobberRisk(it) }
|
||||
}
|
||||
@@ -41,13 +41,24 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"rol2" -> funcRol2(fcall)
|
||||
"ror" -> funcRor(fcall)
|
||||
"ror2" -> funcRor2(fcall)
|
||||
"setlsb" -> funcSetLsbMsb(fcall, false)
|
||||
"setmsb" -> funcSetLsbMsb(fcall, true)
|
||||
"sort" -> funcSort(fcall)
|
||||
"reverse" -> funcReverse(fcall)
|
||||
"memory" -> funcMemory(fcall, discardResult, resultRegister)
|
||||
"peekw" -> funcPeekW(fcall, resultRegister)
|
||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||
"pokew" -> funcPokeW(fcall)
|
||||
"pokemon" -> { /* meme function */ }
|
||||
"pokemon" -> {
|
||||
val memread = PtMemoryByte(fcall.position)
|
||||
memread.add(fcall.args[0])
|
||||
memread.parent = fcall
|
||||
asmgen.assignExpressionToRegister(memread, RegisterOrPair.A, false)
|
||||
asmgen.out(" pha")
|
||||
val memtarget = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.position, memory=memread)
|
||||
asmgen.assignExpressionTo(fcall.args[1], memtarget)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
|
||||
"push" -> asmgen.pushCpuStack(DataType.UBYTE, fcall.args[0])
|
||||
"pushw" -> asmgen.pushCpuStack(DataType.UWORD, fcall.args[0])
|
||||
@@ -108,8 +119,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val var3name = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
|
||||
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
|
||||
val remainderTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[3].position, var3name)
|
||||
assignAsmGen.assignRegisterByte(remainderTarget, CpuRegister.A, false)
|
||||
assignAsmGen.assignRegisterByte(divisionTarget, CpuRegister.Y, false)
|
||||
assignAsmGen.assignRegisterByte(remainderTarget, CpuRegister.A, false, false)
|
||||
assignAsmGen.assignRegisterByte(divisionTarget, CpuRegister.Y, false, false)
|
||||
}
|
||||
|
||||
private fun funcDivmodW(fcall: PtBuiltinFunctionCall) {
|
||||
@@ -181,7 +192,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1")
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
|
||||
asmgen.out("""
|
||||
jsr cx16.jsrfar
|
||||
jsr cx16.JSRFAR
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
// note that by convention the values in A+Y registers are now the return value of the call.
|
||||
@@ -271,11 +282,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
when(fcall.args[0].type) {
|
||||
DataType.UBYTE -> {
|
||||
asmgen.out(" ldy #0 | jsr prog8_lib.func_sqrt16_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
|
||||
@@ -609,6 +620,85 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.assignExpressionToVariable(indexer.index, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE)
|
||||
}
|
||||
|
||||
private fun funcSetLsbMsb(fcall: PtBuiltinFunctionCall, msb: Boolean) {
|
||||
val target: AsmAssignTarget
|
||||
when(fcall.args[0]) {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(fcall.args[0] as PtIdentifier) + if(msb) "+1" else ""
|
||||
target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, variableAsmName = varname)
|
||||
}
|
||||
is PtNumber -> {
|
||||
val num = (fcall.args[0] as PtNumber).number + if(msb) 1 else 0
|
||||
val mem = PtMemoryByte(fcall.position)
|
||||
mem.add(PtNumber(DataType.UBYTE, num, fcall.position))
|
||||
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
|
||||
}
|
||||
is PtAddressOf -> {
|
||||
val mem = PtMemoryByte(fcall.position)
|
||||
if(msb) {
|
||||
val address = PtBinaryExpression("+", DataType.UWORD, fcall.args[0].position)
|
||||
address.add(fcall.args[0])
|
||||
address.add(PtNumber(address.type, 1.0, fcall.args[0].position))
|
||||
mem.add(address)
|
||||
} else {
|
||||
mem.add(fcall.args[0])
|
||||
}
|
||||
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, memory = mem)
|
||||
}
|
||||
is PtArrayIndexer -> {
|
||||
val indexer = fcall.args[0] as PtArrayIndexer
|
||||
require(!indexer.usesPointerVariable)
|
||||
val elementSize: Int
|
||||
val msbAdd: Int
|
||||
if(indexer.splitWords) {
|
||||
val arrayVariable = indexer.variable
|
||||
indexer.children[0] = PtIdentifier(arrayVariable.name + if(msb) "_msb" else "_lsb", DataType.ARRAY_UB, arrayVariable.position)
|
||||
indexer.children[0].parent = indexer
|
||||
elementSize = 1
|
||||
msbAdd = 0
|
||||
} else {
|
||||
elementSize = 2
|
||||
msbAdd = if(msb) 1 else 0
|
||||
}
|
||||
|
||||
// double the index because of word array (if not split), add one if msb (if not split)
|
||||
val constIndexNum = (indexer.index as? PtNumber)?.number
|
||||
if(constIndexNum!=null) {
|
||||
indexer.children[1] = PtNumber(indexer.index.type, constIndexNum*elementSize + msbAdd, indexer.position)
|
||||
indexer.children[1].parent = indexer
|
||||
} else {
|
||||
val multipliedIndex: PtExpression
|
||||
if(elementSize==1) {
|
||||
multipliedIndex = indexer.index
|
||||
} else {
|
||||
multipliedIndex = PtBinaryExpression("<<", indexer.index.type, indexer.position)
|
||||
multipliedIndex.add(indexer.index)
|
||||
multipliedIndex.add(PtNumber(DataType.UBYTE, 1.0, indexer.position))
|
||||
}
|
||||
if(msbAdd>0) {
|
||||
val msbIndex = PtBinaryExpression("+", indexer.index.type, indexer.position)
|
||||
msbIndex.add(multipliedIndex)
|
||||
msbIndex.add(PtNumber(DataType.UBYTE, msbAdd.toDouble(), indexer.position))
|
||||
indexer.children[1] = msbIndex
|
||||
msbIndex.parent = indexer
|
||||
} else {
|
||||
indexer.children[1] = multipliedIndex
|
||||
multipliedIndex.parent=indexer
|
||||
}
|
||||
}
|
||||
target = AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, DataType.UBYTE, fcall.definingSub(), fcall.position, array = indexer)
|
||||
}
|
||||
else -> throw AssemblyError("setlsb/setmsb on weird target ${fcall.args[0]}")
|
||||
}
|
||||
|
||||
if(fcall.args[1].asConstInteger() == 0) {
|
||||
assignAsmGen.assignConstantByte(target, 0)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A, false)
|
||||
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
val dt = fcall.args.single().type
|
||||
@@ -620,7 +710,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true, true)
|
||||
}
|
||||
|
||||
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
@@ -633,7 +723,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
in SplitWordArrayTypes -> TODO("split word any/all")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes, true)
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
@@ -642,7 +732,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
when (dt) {
|
||||
DataType.BYTE -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_b_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A,false)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A,false, true)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||
@@ -790,7 +880,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
assignAsmGen.assignRegisterByte(targetReg, CpuRegister.A, signed)
|
||||
assignAsmGen.assignRegisterByte(targetReg, CpuRegister.A, signed, true)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
@@ -806,55 +896,59 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
|
||||
private fun funcMin(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val signed = fcall.type in SignedDatatypes
|
||||
if(fcall.type in ByteDatatypes) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if(signed) asmgen.out(" bmi +") else asmgen.out(" bcc +")
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.A, targetReg)
|
||||
} else if(fcall.type in WordDatatypes) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if(signed) {
|
||||
when (fcall.type) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if(signed) asmgen.out(" bmi +") else asmgen.out(" bcc +")
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W2+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bpl +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2+1
|
||||
bcc ++
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bcc ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
+""")
|
||||
lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.A, targetReg)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if(signed) {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W2+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bpl +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2+1
|
||||
bcc ++
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bcc ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
+""")
|
||||
}
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("min float not supported")
|
||||
}
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
|
||||
} else {
|
||||
throw AssemblyError("min float not supported")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,17 +1080,17 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
} else {
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" tya")
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.out(" pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
@@ -1030,37 +1124,55 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
when(resultRegister) {
|
||||
if(arg is PtArrayIndexer && resultRegister in setOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
|
||||
// just read the lsb byte out of the word array
|
||||
val arrayVar = if(arg.splitWords) asmgen.asmVariableName(arg.variable)+"_lsb" else asmgen.asmVariableName(arg.variable)
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, arg.type, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar,y")
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, arg.type, CpuRegister.X)
|
||||
asmgen.out(" lda $arrayVar,x")
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, arg.type, CpuRegister.Y)
|
||||
asmgen.out(" ldx $arrayVar,y")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
|
||||
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
|
||||
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
|
||||
// NOTE: we rely on the fact that the above assignment to XY, assigns the Lsb to X as the last instruction.
|
||||
// this is required because the compiler assumes the status bits are set according to what X is (lsb)
|
||||
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" tay | cpy #0")
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" ldy #0 | cmp #0")
|
||||
}
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AX)
|
||||
asmgen.out(" ldx #0 | cmp #0")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.XY)
|
||||
asmgen.out(" ldy #0 | cpx #0")
|
||||
}
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args.single(), resultRegister)
|
||||
asmgen.assignExpressionToRegister(arg, resultRegister)
|
||||
val zero = PtNumber(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
zero.parent=fcall
|
||||
assignAsmGen.assignExpressionToVariable(zero, "cx16.${resultRegister.toString().lowercase()}H", DataType.UBYTE)
|
||||
|
||||
@@ -69,37 +69,59 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
}
|
||||
|
||||
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||
} else {
|
||||
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.parameters)) {
|
||||
registerArgsViaCpuStackEvaluation(call, sub)
|
||||
} else {
|
||||
asmsub6502ArgsEvalOrder(sub).forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
}
|
||||
|
||||
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
|
||||
return when(arg) {
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if (arg.name == "lsb" || arg.name == "msb")
|
||||
return usesOtherRegistersWhileEvaluating(arg.args[0])
|
||||
if (arg.name == "mkword")
|
||||
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
|
||||
return !arg.isSimple()
|
||||
}
|
||||
is PtAddressOf -> false
|
||||
is PtIdentifier -> false
|
||||
is PtMachineRegister -> false
|
||||
is PtMemoryByte -> false
|
||||
is PtNumber -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerArgsViaCpuStackEvaluation(call: PtFunctionCall, callee: PtAsmSub) {
|
||||
// this is called when one or more of the arguments are 'complex' and
|
||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
|
||||
val registersUsed = mutableListOf<RegisterOrStatusflag>();
|
||||
|
||||
if(callee.parameters.isEmpty())
|
||||
return
|
||||
fun usedA() = registersUsed.any {it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY}
|
||||
fun usedX() = registersUsed.any {it.registerOrPair==RegisterOrPair.X || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.XY}
|
||||
fun usedY() = registersUsed.any {it.registerOrPair==RegisterOrPair.Y || it.registerOrPair==RegisterOrPair.AY || it.registerOrPair==RegisterOrPair.XY}
|
||||
|
||||
// use the cpu hardware stack as intermediate storage for the arguments.
|
||||
val argOrder = asmsub6502ArgsEvalOrder(callee)
|
||||
argOrder.reversed().forEach {
|
||||
asmgen.pushCpuStack(callee.parameters[it].second.type, call.args[it])
|
||||
}
|
||||
argOrder.forEach {
|
||||
val param = callee.parameters[it]
|
||||
asmgen.popCpuStack(callee, param.second, param.first)
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||
} else {
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
|
||||
optimalEvalOrder.forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) {
|
||||
if(!registersUsed.any{it.statusflag!=null || it.registerOrPair in CpuRegisters})
|
||||
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
else if(registersUsed.any {it.statusflag!=null}) {
|
||||
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
|
||||
}
|
||||
else {
|
||||
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
|
||||
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
if(usedX()) asmgen.restoreRegisterStack(CpuRegister.X, true)
|
||||
used
|
||||
}
|
||||
} else {
|
||||
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +134,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.type)
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null) {
|
||||
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag {
|
||||
// pass argument via a register parameter
|
||||
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
@@ -141,13 +163,14 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val sourceName = asmgen.asmVariableName(value)
|
||||
// note: cannot use X register here to store A because it might be used for other arguments
|
||||
asmgen.out("""
|
||||
tax
|
||||
pha
|
||||
clc
|
||||
lda $sourceName
|
||||
beq +
|
||||
sec
|
||||
+ txa""")
|
||||
+ pla""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
@@ -155,6 +178,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
}
|
||||
} else throw AssemblyError("can only use Carry as status flag parameter")
|
||||
return RegisterOrStatusflag(null, statusflag)
|
||||
}
|
||||
else {
|
||||
// via register or register pair
|
||||
@@ -187,6 +211,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
|
||||
}
|
||||
return RegisterOrStatusflag(register, null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,3 +233,5 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -497,7 +497,15 @@ internal class ProgramAndVarsGen(
|
||||
for ((scopedName, zpvar) in zpVariables) {
|
||||
if (scopedName.startsWith("cx16.r"))
|
||||
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
||||
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
||||
val variable = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||
if(variable.dt in SplitWordArrayTypes) {
|
||||
val lsbAddr = zpvar.address
|
||||
val msbAddr = zpvar.address + (zpvar.size/2).toUInt()
|
||||
asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)")
|
||||
asmgen.out("${scopedName.substringAfterLast('.')}_msb \t= $msbAddr \t; zp ${zpvar.dt} (msbs)")
|
||||
} else {
|
||||
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ internal class AnyExprAsmGen(
|
||||
return assignByteBinExpr(expr, assign)
|
||||
if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||
require(expr.operator in ComparisonOperators)
|
||||
TODO("words operands comparison -> byte")
|
||||
throw AssemblyError("words operands comparison -> byte, should have been handled by assignOptimizedComparisonWords()")
|
||||
}
|
||||
if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
|
||||
require(expr.operator in ComparisonOperators)
|
||||
return assignFloatBinExpr(expr, assign)
|
||||
}
|
||||
TODO("weird expr operand types")
|
||||
throw AssemblyError("weird expr operand types: ${expr.left.type} and {${expr.right.type}")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+430
-281
@@ -83,19 +83,20 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
val block = target.origAstTarget?.definingBlock()
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, value.asmVarname, value.datatype)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, regName(value), value.datatype)
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(target.asmVarname, target.datatype, operator, value.number!!.number.toInt(), block)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, value.asmVarname, value.datatype, block)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(target.asmVarname, target.datatype, operator, regName(value), value.datatype, block)
|
||||
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(target.asmVarname, target.datatype, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.array!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.array!!, block)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression)
|
||||
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression, block)
|
||||
}
|
||||
else {
|
||||
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression!!)
|
||||
inplacemodificationWordWithValue(target.asmVarname, target.datatype, operator, value.expression!!, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,237 +205,251 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val indexNum = target.array!!.index as? PtNumber
|
||||
val indexVar = target.array.index as? PtIdentifier
|
||||
when {
|
||||
indexNum!=null -> {
|
||||
val targetVarName = if(target.array.splitWords)
|
||||
"${target.asmVarname} + ${indexNum.number.toInt()}"
|
||||
else
|
||||
"${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteVariableWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression!!)
|
||||
}
|
||||
if (indexNum!=null) {
|
||||
val targetVarName = if(target.array.splitWords)
|
||||
"${target.asmVarname} + ${indexNum.number.toInt()}"
|
||||
else
|
||||
"${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteVariableWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, value.asmVarname, value.datatype)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, regName(value), value.datatype)
|
||||
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(targetVarName, target.datatype, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(targetVarName, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float array")
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(targetVarName, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationFloatWithValue(targetVarName, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationFloatWithValue(targetVarName, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
|
||||
in WordDatatypes -> {
|
||||
val block = target.origAstTarget?.definingBlock()
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt(), block)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, value.asmVarname, value.datatype, block)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, regName(value), value.datatype, block)
|
||||
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(targetVarName, target.datatype, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.array!!, block)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression, block)
|
||||
} else {
|
||||
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DataType.FLOAT -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(targetVarName, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float array")
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(targetVarName, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationFloatWithValue(targetVarName, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationFloatWithValue(targetVarName, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
indexVar!=null -> {
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
if(value.kind==SourceStorageKind.EXPRESSION
|
||||
&& value.expression is PtTypeCast
|
||||
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
}
|
||||
else {
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
if(value.kind==SourceStorageKind.EXPRESSION
|
||||
&& value.expression is PtTypeCast
|
||||
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda ${target.array.variable.name},y")
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, "#${value.number!!.number.toInt()}", target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, value.asmVarname, target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
|
||||
SourceStorageKind.REGISTER -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, regName(value), target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
|
||||
SourceStorageKind.MEMORY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.memory!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
SourceStorageKind.ARRAY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.array!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression)
|
||||
else
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
asmgen.out(" sta ${target.array.variable.name},y")
|
||||
}
|
||||
|
||||
in WordDatatypes -> {
|
||||
if(value.kind==SourceStorageKind.EXPRESSION
|
||||
&& value.expression is PtTypeCast
|
||||
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(target.array.splitWords) {
|
||||
asmgen.out(" lda ${target.array.variable.name}_lsb,y")
|
||||
asmgen.out(" ldx ${target.array.variable.name}_msb,y")
|
||||
} else {
|
||||
asmgen.out(" lda ${target.array.variable.name},y")
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, "#${value.number!!.number.toInt()}", target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, value.asmVarname, target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, regName(value), target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
SourceStorageKind.MEMORY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.memory!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
SourceStorageKind.ARRAY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.array!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression)
|
||||
else
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
asmgen.out(" sta ${target.array.variable.name},y")
|
||||
asmgen.out(" ldx ${target.array.variable.name}+1,y")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(value.kind==SourceStorageKind.EXPRESSION
|
||||
&& value.expression is PtTypeCast
|
||||
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(target.array.splitWords) {
|
||||
asmgen.out(" lda ${target.array.variable.name}_lsb,y")
|
||||
asmgen.out(" ldx ${target.array.variable.name}_msb,y")
|
||||
} else {
|
||||
asmgen.out(" lda ${target.array.variable.name},y")
|
||||
asmgen.out(" ldx ${target.array.variable.name}+1,y")
|
||||
}
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(!inplacemodificationRegisterAXwithLiteralval(operator, number)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithLiteralval("P8ZP_SCRATCH_W1", target.datatype, operator, number)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
if(!inplacemodificationRegisterAXwithVariable(
|
||||
operator,
|
||||
value.asmVarname,
|
||||
value.datatype
|
||||
)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
if(!inplacemodificationRegisterAXwithVariable(
|
||||
operator,
|
||||
regName(value),
|
||||
value.datatype
|
||||
)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.MEMORY -> {
|
||||
val block = target.origAstTarget?.definingBlock()
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(!inplacemodificationRegisterAXwithLiteralval(operator, number)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithMemread("P8ZP_SCRATCH_W1", target.datatype, operator, value.memory!!)
|
||||
inplacemodificationWordWithLiteralval("P8ZP_SCRATCH_W1", target.datatype, operator, number, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
SourceStorageKind.ARRAY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression)
|
||||
else
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
if(target.array.splitWords)
|
||||
asmgen.out(" sta ${target.array.variable.name}_lsb,y | txa | sta ${target.array.variable.name}_msb,y")
|
||||
else
|
||||
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
// copy array value into tempvar
|
||||
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
pha ; save array ptr lsb
|
||||
tya
|
||||
pha ; save array ptr msb
|
||||
lda #<$tempvar
|
||||
ldy #>$tempvar
|
||||
jsr floats.copy_float""")
|
||||
|
||||
// calculate on tempvar
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(tempvar, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(tempvar, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(tempvar, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float")
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(tempvar, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
inplacemodificationFloatWithValue(tempvar, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationFloatWithValue(tempvar, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy tempvar back into array
|
||||
asmgen.out("""
|
||||
lda #<$tempvar
|
||||
ldy #>$tempvar
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
pla ; restore array ptr msb
|
||||
tay
|
||||
pla ; restore array ptr lsb
|
||||
jsr floats.copy_float""")
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
if(!inplacemodificationRegisterAXwithVariable(
|
||||
operator,
|
||||
value.asmVarname,
|
||||
value.datatype
|
||||
)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
|
||||
SourceStorageKind.REGISTER -> {
|
||||
if(!inplacemodificationRegisterAXwithVariable(
|
||||
operator,
|
||||
regName(value),
|
||||
value.datatype
|
||||
)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
|
||||
SourceStorageKind.MEMORY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithMemread("P8ZP_SCRATCH_W1", target.datatype, operator, value.memory!!)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
|
||||
SourceStorageKind.ARRAY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression, block)
|
||||
else
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
if(target.array.splitWords)
|
||||
asmgen.out(" sta ${target.array.variable.name}_lsb,y | txa | sta ${target.array.variable.name}_msb,y")
|
||||
else
|
||||
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
|
||||
}
|
||||
|
||||
DataType.FLOAT -> {
|
||||
// copy array value into tempvar
|
||||
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
pha ; save array ptr lsb
|
||||
tya
|
||||
pha ; save array ptr msb
|
||||
lda #<$tempvar
|
||||
ldy #>$tempvar
|
||||
jsr floats.copy_float""")
|
||||
|
||||
// calculate on tempvar
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(tempvar, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(tempvar, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(tempvar, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float")
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(tempvar, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
inplacemodificationFloatWithValue(tempvar, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationFloatWithValue(tempvar, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy tempvar back into array
|
||||
asmgen.out("""
|
||||
lda #<$tempvar
|
||||
ldy #>$tempvar
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
pla ; restore array ptr msb
|
||||
tay
|
||||
pla ; restore array ptr lsb
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
else -> throw AssemblyError("indexer expression should have been replaced by auto indexer var")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
|
||||
@@ -442,7 +457,6 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun inplacemodificationRegisterAXwithVariable(operator: String, variable: String, varDt: DataType): Boolean {
|
||||
// note: we only optimize addition and subtraction, and these are the same for unsigned or signed.
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
return if(varDt in WordDatatypes) {
|
||||
@@ -492,12 +506,44 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
true
|
||||
}
|
||||
}
|
||||
else -> return false // TODO optimize more operators, such as the bitwise logical ones? Might need to know if signed
|
||||
"|" -> {
|
||||
asmgen.out("""
|
||||
ora $variable
|
||||
tay
|
||||
txa
|
||||
ora $variable+1
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
"&" -> {
|
||||
asmgen.out("""
|
||||
and $variable
|
||||
tay
|
||||
txa
|
||||
and $variable+1
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
"^" -> {
|
||||
asmgen.out("""
|
||||
eor $variable
|
||||
tay
|
||||
txa
|
||||
eor $variable+1
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplacemodificationRegisterAXwithLiteralval(operator: String, number: Int): Boolean {
|
||||
// note: we only optimize addition and subtraction, and these are the same for unsigned or signed.
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
return if(number in -128..255) {
|
||||
@@ -541,7 +587,40 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
true
|
||||
}
|
||||
}
|
||||
else -> return false // TODO optimize more operators, such as the bitwise logical ones? Might need to know if signed
|
||||
"|" -> {
|
||||
asmgen.out("""
|
||||
ora #<$number
|
||||
tay
|
||||
txa
|
||||
ora #>$number
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
"&" -> {
|
||||
asmgen.out("""
|
||||
and #<$number
|
||||
tay
|
||||
txa
|
||||
and #>$number
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
"^" -> {
|
||||
asmgen.out("""
|
||||
eor #<$number
|
||||
tay
|
||||
txa
|
||||
eor #>$number
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -915,8 +994,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
tay
|
||||
beq +
|
||||
lda $variable
|
||||
cpy #0
|
||||
beq +
|
||||
- asl a
|
||||
dey
|
||||
bne -
|
||||
@@ -926,8 +1006,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
if(!signed) {
|
||||
asmgen.out("""
|
||||
tay
|
||||
beq +
|
||||
lda $variable
|
||||
cpy #0
|
||||
beq +
|
||||
- lsr a
|
||||
dey
|
||||
bne -
|
||||
@@ -935,8 +1016,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
} else {
|
||||
asmgen.out("""
|
||||
tay
|
||||
beq +
|
||||
lda $variable
|
||||
cpy #0
|
||||
beq +
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- asl a
|
||||
ror P8ZP_SCRATCH_B1
|
||||
@@ -1277,12 +1359,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
else -> {
|
||||
inplacemodificationWordWithValue(name, dt, operator, memread)
|
||||
inplacemodificationWordWithValue(name, dt, operator, memread, memread.definingBlock())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplacemodificationWordWithLiteralval(name: String, dt: DataType, operator: String, value: Int) {
|
||||
private fun inplacemodificationWordWithLiteralval(name: String, dt: DataType, operator: String, value: Int, block: PtBlock?) {
|
||||
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
@@ -1342,16 +1424,31 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
if(value in asmgen.optimizedWordMultiplications) {
|
||||
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sta math.multiply_words.multiplier
|
||||
lda $name+1
|
||||
sta math.multiply_words.multiplier+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.multiply_words
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
if(block?.veraFxMuls==true)
|
||||
// cx16 verafx hardware mul
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
jsr verafx.muls
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sta math.multiply_words.multiplier
|
||||
lda $name+1
|
||||
sta math.multiply_words.multiplier+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.multiply_words
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
}
|
||||
}
|
||||
"/" -> {
|
||||
@@ -1741,7 +1838,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplacemodificationWordWithVariable(name: String, dt: DataType, operator: String, otherName: String, valueDt: DataType) {
|
||||
private fun inplacemodificationWordWithVariable(name: String, dt: DataType, operator: String, otherName: String, valueDt: DataType, block: PtBlock?) {
|
||||
when (valueDt) {
|
||||
in ByteDatatypes -> {
|
||||
// the other variable is a BYTE type so optimize for that
|
||||
@@ -1794,23 +1891,46 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
sta $name+1""")
|
||||
}
|
||||
"*" -> {
|
||||
if(valueDt==DataType.UBYTE) {
|
||||
asmgen.out(" lda $otherName | sta math.multiply_words.multiplier")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz math.multiply_words.multiplier+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta math.multiply_words.multiplier+1")
|
||||
} else {
|
||||
asmgen.out(" lda $otherName")
|
||||
asmgen.signExtendAYlsb(valueDt)
|
||||
asmgen.out(" sta math.multiply_words.multiplier | sty math.multiply_words.multiplier+1")
|
||||
}
|
||||
asmgen.out("""
|
||||
if(block?.veraFxMuls==true) {
|
||||
// cx16 verafx hardware muls
|
||||
if(valueDt==DataType.UBYTE) {
|
||||
asmgen.out(" lda $otherName | sta cx16.r1")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz cx16.r1+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta cx16.r1+1")
|
||||
} else {
|
||||
asmgen.out(" lda $otherName")
|
||||
asmgen.signExtendAYlsb(valueDt)
|
||||
asmgen.out(" sta cx16.r1 | sty cx16.r1+1")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
jsr verafx.muls
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
} else {
|
||||
if(valueDt==DataType.UBYTE) {
|
||||
asmgen.out(" lda $otherName | sta math.multiply_words.multiplier")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz math.multiply_words.multiplier+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta math.multiply_words.multiplier+1")
|
||||
} else {
|
||||
asmgen.out(" lda $otherName")
|
||||
asmgen.signExtendAYlsb(valueDt)
|
||||
asmgen.out(" sta math.multiply_words.multiplier | sty math.multiply_words.multiplier+1")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
}
|
||||
}
|
||||
"/" -> {
|
||||
if(dt==DataType.UWORD) {
|
||||
@@ -1858,12 +1978,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- asl $name
|
||||
rol $name+1
|
||||
dey
|
||||
bne -
|
||||
ldy $otherName
|
||||
beq +
|
||||
- asl $name
|
||||
rol $name+1
|
||||
dey
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
">>" -> {
|
||||
@@ -1939,16 +2059,31 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
"+" -> asmgen.out(" lda $name | clc | adc $otherName | sta $name | lda $name+1 | adc $otherName+1 | sta $name+1")
|
||||
"-" -> asmgen.out(" lda $name | sec | sbc $otherName | sta $name | lda $name+1 | sbc $otherName+1 | sta $name+1")
|
||||
"*" -> {
|
||||
asmgen.out("""
|
||||
lda $otherName
|
||||
ldy $otherName+1
|
||||
sta math.multiply_words.multiplier
|
||||
sty math.multiply_words.multiplier+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
if(block?.veraFxMuls==true)
|
||||
// cx16 verafx hardware muls
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
lda $otherName
|
||||
ldy $otherName+1
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
jsr verafx.muls
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $otherName
|
||||
ldy $otherName+1
|
||||
sta math.multiply_words.multiplier
|
||||
sty math.multiply_words.multiplier+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
sta $name
|
||||
sty $name+1""")
|
||||
}
|
||||
"/" -> {
|
||||
if(dt==DataType.WORD) {
|
||||
@@ -2126,17 +2261,31 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
|
||||
private fun inplacemodificationWordWithValue(name: String, dt: DataType, operator: String, value: PtExpression, block: PtBlock?) {
|
||||
fun multiplyVarByWordInAY() {
|
||||
asmgen.out("""
|
||||
sta math.multiply_words.multiplier
|
||||
sty math.multiply_words.multiplier+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
if(block?.veraFxMuls==true)
|
||||
// cx16 verafx hardware muls
|
||||
asmgen.out("""
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
jsr verafx.muls
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sta math.multiply_words.multiplier
|
||||
sty math.multiply_words.multiplier+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
}
|
||||
|
||||
fun divideVarByWordInAY() {
|
||||
|
||||
@@ -22,6 +22,7 @@ class TestCodegen: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
zpReserved = emptyList(),
|
||||
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = target,
|
||||
@@ -44,7 +45,7 @@ class TestCodegen: FunSpec({
|
||||
//}
|
||||
val codegen = AsmGen6502(prefixSymbols = false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
@@ -94,7 +95,7 @@ class TestCodegen: FunSpec({
|
||||
program.add(block)
|
||||
|
||||
// define the "cx16.r0" virtual register
|
||||
val cx16block = PtBlock("cx16", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||
program.add(cx16block)
|
||||
|
||||
|
||||
@@ -36,10 +36,10 @@ dependencies {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,16 +30,16 @@ 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.6.2'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
test {
|
||||
|
||||
@@ -33,13 +33,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"peekw" -> funcPeekW(call)
|
||||
"poke" -> funcPoke(call)
|
||||
"pokew" -> funcPokeW(call)
|
||||
"pokemon" -> ExpressionCodeResult.EMPTY // easter egg function
|
||||
"pokemon" -> funcPokemon(call)
|
||||
"mkword" -> funcMkword(call)
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
|
||||
"sort" -> funcSort(call)
|
||||
"reverse" -> funcReverse(call)
|
||||
"setlsb" -> funcSetLsbMsb(call, false)
|
||||
"setmsb" -> funcSetLsbMsb(call, true)
|
||||
"rol" -> funcRolRor(Opcode.ROXL, call)
|
||||
"ror" -> funcRolRor(Opcode.ROXR, call)
|
||||
"rol2" -> funcRolRor(Opcode.ROL, call)
|
||||
@@ -69,6 +71,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val bankTr = exprGen.translateExpression(call.args[0])
|
||||
val addressTr = exprGen.translateExpression(call.args[1])
|
||||
val argumentwordTr = exprGen.translateExpression(call.args[2])
|
||||
@@ -110,6 +113,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val left = exprGen.translateExpression(call.args[0])
|
||||
val right = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, left, left.resultReg, -1)
|
||||
@@ -144,6 +148,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
@@ -165,6 +170,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
@@ -308,6 +314,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
else -> throw IllegalArgumentException("weird type to reverse")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
@@ -331,6 +338,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
else -> throw IllegalArgumentException("weird type to sort")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
@@ -341,19 +349,25 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val msbTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, msbTr, msbTr.resultReg, -1)
|
||||
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg, reg3 = msbTr.resultReg)
|
||||
if((call.args[0] as? PtNumber)?.number == 0.0) {
|
||||
// msb is 0, use EXT
|
||||
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null)
|
||||
} else {
|
||||
val msbTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, msbTr, msbTr.resultReg, -1)
|
||||
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = msbTr.resultReg, reg3 = lsbTr.resultReg), null)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val type = irType(call.type)
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
val minimumTr = exprGen.translateExpression(call.args[1])
|
||||
@@ -538,6 +552,59 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPokemon(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val address = call.args[0]
|
||||
|
||||
fun pokeM(result: MutableList<IRCodeChunkBase>, address: Int, value: PtExpression) {
|
||||
if(codeGen.isZero(value)) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
|
||||
}
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(value)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pokeI(result: MutableList<IRCodeChunkBase>, register: Int, value: PtExpression) {
|
||||
if(codeGen.isZero(value)) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = register)
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(value)
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = register)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if(address is PtNumber) {
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
val addressNum = address.number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
|
||||
}
|
||||
pokeM(result, addressNum, call.args[1])
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
} else {
|
||||
val addressTr = exprGen.translateExpression(address)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
|
||||
}
|
||||
pokeI(result, addressTr.resultReg, call.args[1])
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun funcMemory(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val name = (call.args[0] as PtString).value
|
||||
val code = IRCodeChunk(null, null)
|
||||
@@ -576,6 +643,122 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcSetLsbMsb(call: PtBuiltinFunctionCall, msb: Boolean): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val target = call.args[0]
|
||||
val isConstZeroValue = call.args[1].asConstInteger()==0
|
||||
when(target) {
|
||||
is PtIdentifier -> {
|
||||
if(isConstZeroValue) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
if (msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg)
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
if (msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtArrayIndexer -> {
|
||||
require(!target.usesPointerVariable)
|
||||
if(target.splitWords) {
|
||||
// lsb/msb in split arrays, element index 'size' is always 1
|
||||
val constIndex = target.index.asConstInteger()
|
||||
val varName = target.variable.name + if(msb) "_msb" else "_lsb"
|
||||
if(isConstZeroValue) {
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = varName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = varName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(target.type)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(isConstZeroValue) {
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.nextFree()
|
||||
val offset = eltSize*constIndex + if(msb) 1 else 0
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = target.variable.name)
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(eltSize>1)
|
||||
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
|
||||
if(msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
if(constIndex!=null) {
|
||||
val offsetReg = codeGen.registers.nextFree()
|
||||
val offset = eltSize*constIndex + if(msb) 1 else 0
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = target.variable.name)
|
||||
}
|
||||
} else {
|
||||
val indexTr = exprGen.translateExpression(target.index)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
if(eltSize>1)
|
||||
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
|
||||
if(msb)
|
||||
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird target for setlsb/setmsb: $target")
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
|
||||
}
|
||||
|
||||
|
||||
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
|
||||
val assignment = PtAssignment(target.position)
|
||||
val assignTarget = PtAssignTarget(target.position)
|
||||
|
||||
@@ -3,10 +3,7 @@ package prog8.codegen.intermediate
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StSub
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.PassByValueDatatypes
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.code.core.*
|
||||
import prog8.intermediate.*
|
||||
|
||||
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) {
|
||||
@@ -69,10 +66,28 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val vmDt = irType(expr.type)
|
||||
val symbol = expr.identifier.name
|
||||
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
|
||||
val code = IRCodeChunk(null, null)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, labelSymbol = symbol)
|
||||
ExpressionCodeResult(code, vmDt, resultRegister, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = symbol), null)
|
||||
if(expr.isFromArrayElement) {
|
||||
val indexTr = translateExpression(expr.arrayIndexExpr!!)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
if(expr.identifier.type in SplitWordArrayTypes) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
|
||||
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
|
||||
}
|
||||
} else {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(expr.identifier.type, 1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
// multiply indexTr resultreg by the eltSize and add this to the resultRegister.
|
||||
if(eltSize>1)
|
||||
it += IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=indexTr.resultReg, immediate = eltSize)
|
||||
it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=resultRegister, reg2=indexTr.resultReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
ExpressionCodeResult(result, vmDt, resultRegister, -1)
|
||||
}
|
||||
is PtMemoryByte -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
@@ -107,6 +122,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
when(check.iterable.type) {
|
||||
DataType.STR -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
@@ -115,6 +131,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
@@ -126,6 +143,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
val elementTr = translateExpression(check.element)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(check.iterable)
|
||||
@@ -170,18 +188,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val memOffset = (arrayIx.index as PtNumber).number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val tmpRegMsb = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb+$memOffset")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
|
||||
}
|
||||
} else {
|
||||
val tr = translateExpression(arrayIx.index)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val tmpRegMsb = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
|
||||
}
|
||||
}
|
||||
return ExpressionCodeResult(result, vmDt, finalResultReg, -1)
|
||||
@@ -366,6 +384,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
|
||||
is StSub -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
|
||||
// assign the arguments
|
||||
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
|
||||
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
|
||||
@@ -386,8 +405,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
|
||||
}
|
||||
// create the call
|
||||
val call = IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
|
||||
addInstr(result, call, null)
|
||||
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)), null)
|
||||
return if(fcall.void)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
else if(fcall.type==DataType.FLOAT)
|
||||
@@ -397,6 +415,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
is StRomSub -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
|
||||
// assign the arguments
|
||||
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
|
||||
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
|
||||
@@ -543,14 +562,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
val valueReg = codeGen.registers.nextFree()
|
||||
val label = codeGen.createLabelName()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1), null)
|
||||
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
|
||||
if (notEquals) {
|
||||
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.BEQ, IRDataType.BYTE, reg1=valueReg, immediate = 0, labelSymbol = label), null)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1)
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0)
|
||||
it += if (notEquals)
|
||||
IRInstruction(Opcode.BSTNE, labelSymbol = label)
|
||||
else
|
||||
IRInstruction(Opcode.BSTEQ, labelSymbol = label)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0), null)
|
||||
result += IRCodeChunk(label, null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
} else {
|
||||
|
||||
@@ -415,13 +415,19 @@ class IRCodeGen(
|
||||
if(choice.statements.children.isEmpty()) {
|
||||
// no statements for this choice value, jump to the end immediately
|
||||
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
|
||||
addInstr(result, IRInstruction(Opcode.BEQ, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt(), labelSymbol = endLabel), null)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val choiceLabel = createLabelName()
|
||||
choices.add(choiceLabel to choice)
|
||||
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
|
||||
addInstr(result, IRInstruction(Opcode.BEQ, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt(), labelSymbol = choiceLabel), null)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,7 +470,8 @@ 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.BEQ, IRDataType.BYTE, reg1 = tmpReg, immediate = 0, labelSymbol = endLabel)
|
||||
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)
|
||||
}
|
||||
result += translateNode(forLoop.statements)
|
||||
@@ -484,15 +491,16 @@ class IRCodeGen(
|
||||
val tmpRegLsb = registers.nextFree()
|
||||
val tmpRegMsb = registers.nextFree()
|
||||
val concatReg = registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_msb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegLsb, reg3=tmpRegMsb)
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
|
||||
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
|
||||
}
|
||||
result += translateNode(forLoop.statements)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexReg)
|
||||
it += IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=indexReg, immediate = if(iterableLength==256) 0 else iterableLength, labelSymbol = loopLabel)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(iterableLength==256) 0 else iterableLength)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@@ -507,7 +515,10 @@ class IRCodeGen(
|
||||
}
|
||||
result += translateNode(forLoop.statements)
|
||||
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
|
||||
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=indexReg, immediate = if(lengthBytes==256) 0 else lengthBytes, labelSymbol = loopLabel), null)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(lengthBytes==256) 0 else lengthBytes)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,8 +566,11 @@ class IRCodeGen(
|
||||
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
||||
if(step==1 || step==-1) {
|
||||
// if endvalue == loopvar, stop loop, else iterate
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol), null)
|
||||
addInstr(result, IRInstruction(Opcode.BEQR, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol = labelAfterFor), null)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
|
||||
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
|
||||
}
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
|
||||
} else {
|
||||
@@ -601,8 +615,8 @@ class IRCodeGen(
|
||||
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
||||
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
|
||||
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
|
||||
chunk2 += IRInstruction(Opcode.XOR, loopvarDtIr, reg1 = 999, immediate = 111)
|
||||
chunk2 += IRInstruction(Opcode.BNE, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped, labelSymbol = loopLabel)
|
||||
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
|
||||
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
result += chunk2
|
||||
return result
|
||||
}
|
||||
@@ -961,21 +975,33 @@ class IRCodeGen(
|
||||
fpReg1 = leftTr.resultFpReg,
|
||||
fpReg2 = rightTr.resultFpReg
|
||||
)
|
||||
val gotoOpcode = when (condition.operator) {
|
||||
"==" -> Opcode.BEQ
|
||||
"!=" -> Opcode.BNE
|
||||
"<" -> Opcode.BLTS
|
||||
">" -> Opcode.BGTS
|
||||
"<=" -> Opcode.BLES
|
||||
">=" -> Opcode.BGES
|
||||
else -> throw AssemblyError("weird operator")
|
||||
when(condition.operator) {
|
||||
// TODO: the converted list of operators
|
||||
"==" -> {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
|
||||
it += branchInstr(goto, Opcode.BSTEQ)
|
||||
}
|
||||
"!=" -> {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
|
||||
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
|
||||
"<=" -> Opcode.BLES
|
||||
">=" -> Opcode.BGES
|
||||
else -> throw AssemblyError("weird operator")
|
||||
}
|
||||
it += if (goto.address != null)
|
||||
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.address?.toInt())
|
||||
else if (goto.generatedLabel != null)
|
||||
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.generatedLabel)
|
||||
else
|
||||
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.identifier!!.name)
|
||||
}
|
||||
}
|
||||
it += if (goto.address != null)
|
||||
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.address?.toInt())
|
||||
else if (goto.generatedLabel != null)
|
||||
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.generatedLabel)
|
||||
else
|
||||
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.identifier!!.name)
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
@@ -990,6 +1016,14 @@ 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)
|
||||
IRInstruction(branchOpcode, labelSymbol = goto.generatedLabel)
|
||||
else
|
||||
IRInstruction(branchOpcode, labelSymbol = goto.identifier!!.name)
|
||||
|
||||
private fun ifZeroIntThenJump(
|
||||
result: MutableList<IRCodeChunkBase>,
|
||||
ifElse: PtIfElse,
|
||||
@@ -1000,21 +1034,33 @@ class IRCodeGen(
|
||||
val condition = ifElse.condition as PtBinaryExpression
|
||||
val leftTr = expressionEval.translateExpression(condition.left)
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
val opcode = when (condition.operator) {
|
||||
"==" -> Opcode.BEQ
|
||||
"!=" -> Opcode.BNE
|
||||
"<" -> if (signed) Opcode.BLTS else Opcode.BLT
|
||||
">" -> if (signed) Opcode.BGTS else Opcode.BGT
|
||||
"<=" -> if (signed) Opcode.BLES else Opcode.BLE
|
||||
">=" -> if (signed) Opcode.BGES else Opcode.BGE
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
when(condition.operator) {
|
||||
// TODO: converted list of operators
|
||||
"==" -> {
|
||||
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)
|
||||
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
|
||||
"<=" -> if (signed) Opcode.BLES else Opcode.BLE
|
||||
">=" -> if (signed) Opcode.BGES else Opcode.BGE
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
if (goto.address != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
|
||||
else if (goto.generatedLabel != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
|
||||
}
|
||||
}
|
||||
if (goto.address != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
|
||||
else if (goto.generatedLabel != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = leftTr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
|
||||
}
|
||||
|
||||
private fun ifNonZeroIntThenJump(
|
||||
@@ -1028,47 +1074,60 @@ class IRCodeGen(
|
||||
if(condition==null) {
|
||||
val tr = expressionEval.translateExpression(ifElse.condition)
|
||||
result += tr.chunks
|
||||
if (goto.address != null)
|
||||
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, address = goto.address?.toInt()), null)
|
||||
else if (goto.generatedLabel != null)
|
||||
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, labelSymbol = goto.generatedLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.BNE, irDtLeft, reg1 = tr.resultReg, immediate = 0, labelSymbol = goto.identifier!!.name), null)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, irDtLeft, reg1 = tr.resultReg, immediate = 0)
|
||||
it += branchInstr(goto, Opcode.BSTNE)
|
||||
}
|
||||
} else {
|
||||
val leftTr = expressionEval.translateExpression(condition.left)
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
val opcode: Opcode
|
||||
val number = (condition.right as? PtNumber)?.number?.toInt()
|
||||
if(number!=null) {
|
||||
val firstReg = leftTr.resultReg
|
||||
opcode = when (condition.operator) {
|
||||
"==" -> Opcode.BEQ
|
||||
"!=" -> Opcode.BNE
|
||||
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
|
||||
">" -> if(signed) Opcode.BGTS else Opcode.BGT
|
||||
"<=" -> if(signed) Opcode.BLES else Opcode.BLE
|
||||
">=" -> if(signed) Opcode.BGES else Opcode.BGE
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
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)
|
||||
}
|
||||
"!=" -> {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = firstReg, immediate = number), 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
|
||||
"<=" -> if(signed) Opcode.BLES else Opcode.BLE
|
||||
">=" -> if(signed) Opcode.BGES else Opcode.BGE
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
if (goto.address != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, address = goto.address?.toInt()), null)
|
||||
else if (goto.generatedLabel != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.generatedLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.identifier!!.name), null)
|
||||
}
|
||||
}
|
||||
if (goto.address != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, address = goto.address?.toInt()), null)
|
||||
else if (goto.generatedLabel != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.generatedLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, immediate = number, labelSymbol = goto.identifier!!.name), null)
|
||||
} else {
|
||||
val rightTr = expressionEval.translateExpression(condition.right)
|
||||
addToResult(result, rightTr, rightTr.resultReg, -1)
|
||||
val firstReg: Int
|
||||
val secondReg: Int
|
||||
val opcode: Opcode
|
||||
var useCmp = false
|
||||
when (condition.operator) {
|
||||
"==" -> {
|
||||
opcode = Opcode.BEQR
|
||||
useCmp = true
|
||||
opcode = Opcode.BSTEQ
|
||||
firstReg = leftTr.resultReg
|
||||
secondReg = rightTr.resultReg
|
||||
}
|
||||
"!=" -> {
|
||||
opcode = Opcode.BNER
|
||||
useCmp = true
|
||||
opcode = Opcode.BSTNE
|
||||
firstReg = leftTr.resultReg
|
||||
secondReg = rightTr.resultReg
|
||||
}
|
||||
@@ -1096,12 +1155,20 @@ class IRCodeGen(
|
||||
}
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
if (goto.address != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, address = goto.address?.toInt()), null)
|
||||
else if (goto.generatedLabel != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.generatedLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.identifier!!.name), null)
|
||||
|
||||
if(useCmp) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMP, irDtLeft, reg1 = firstReg, reg2 = secondReg)
|
||||
it += branchInstr(goto, opcode)
|
||||
}
|
||||
} else {
|
||||
if (goto.address != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, address = goto.address?.toInt()), null)
|
||||
else if (goto.generatedLabel != null)
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.generatedLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(opcode, irDtLeft, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.identifier!!.name), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1109,6 +1176,7 @@ class IRCodeGen(
|
||||
private fun translateIfElseZeroComparison(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val elseBranch: Opcode
|
||||
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
|
||||
val compResultReg: Int
|
||||
val branchDt: IRDataType
|
||||
val condition = ifElse.condition as PtBinaryExpression
|
||||
@@ -1122,13 +1190,19 @@ class IRCodeGen(
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = rightFpReg, immediateFp = 0f)
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightFpReg)
|
||||
}
|
||||
elseBranch = when (condition.operator) {
|
||||
"==" -> Opcode.BNE
|
||||
"!=" -> Opcode.BEQ
|
||||
"<" -> Opcode.BGES
|
||||
">" -> Opcode.BLES
|
||||
"<=" -> Opcode.BGTS
|
||||
">=" -> Opcode.BLTS
|
||||
when (condition.operator) {
|
||||
"==" -> {
|
||||
elseBranch = Opcode.BSTNE
|
||||
useCmpi = true
|
||||
}
|
||||
"!=" -> {
|
||||
elseBranch = Opcode.BSTEQ
|
||||
useCmpi = true
|
||||
}
|
||||
"<" -> elseBranch = Opcode.BGES
|
||||
">" -> elseBranch = Opcode.BLES
|
||||
"<=" -> elseBranch = Opcode.BGTS
|
||||
">=" -> elseBranch = Opcode.BLTS
|
||||
else -> throw AssemblyError("weird operator")
|
||||
}
|
||||
} else {
|
||||
@@ -1137,13 +1211,19 @@ class IRCodeGen(
|
||||
val tr = expressionEval.translateExpression(condition.left)
|
||||
compResultReg = tr.resultReg
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
elseBranch = when (condition.operator) {
|
||||
"==" -> Opcode.BNE
|
||||
"!=" -> Opcode.BEQ
|
||||
"<" -> if (signed) Opcode.BGES else Opcode.BGE
|
||||
">" -> if (signed) Opcode.BLES else Opcode.BLE
|
||||
"<=" -> if (signed) Opcode.BGTS else Opcode.BGT
|
||||
">=" -> if (signed) Opcode.BLTS else Opcode.BLT
|
||||
when (condition.operator) {
|
||||
"==" -> {
|
||||
elseBranch = Opcode.BSTNE
|
||||
useCmpi = true
|
||||
}
|
||||
"!=" -> {
|
||||
elseBranch = Opcode.BSTEQ
|
||||
useCmpi = true
|
||||
}
|
||||
"<" -> elseBranch = if (signed) Opcode.BGES else Opcode.BGE
|
||||
">" -> elseBranch = if (signed) Opcode.BLES else Opcode.BLE
|
||||
"<=" -> elseBranch = if (signed) Opcode.BGTS else Opcode.BGT
|
||||
">=" -> elseBranch = if (signed) Opcode.BLTS else Opcode.BLT
|
||||
else -> throw AssemblyError("weird operator")
|
||||
}
|
||||
}
|
||||
@@ -1151,14 +1231,26 @@ class IRCodeGen(
|
||||
if(ifElse.elseScope.children.isEmpty()) {
|
||||
// just if
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
|
||||
if(useCmpi) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, branchDt, reg1=compResultReg, immediate = 0)
|
||||
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
|
||||
}
|
||||
} else
|
||||
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
result += IRCodeChunk(afterIfLabel, null)
|
||||
} else {
|
||||
// if and else
|
||||
val elseLabel = createLabelName()
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = elseLabel), null)
|
||||
if(useCmpi) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, branchDt, reg1=compResultReg, immediate = 0)
|
||||
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
|
||||
}
|
||||
} else
|
||||
addInstr(result, IRInstruction(elseBranch, branchDt, reg1=compResultReg, immediate = 0, labelSymbol = elseLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
|
||||
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
|
||||
@@ -1169,31 +1261,39 @@ class IRCodeGen(
|
||||
|
||||
private fun translateIfElseNonZeroComparison(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val elseBranchOpcode: Opcode
|
||||
val elseBranchFirstReg: Int
|
||||
val elseBranchSecondReg: Int
|
||||
val branchDt: IRDataType
|
||||
val condition = ifElse.condition as? PtBinaryExpression
|
||||
if(condition==null) {
|
||||
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()
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(result, IRInstruction(Opcode.BEQ, irDtLeft, reg1=tr.resultReg, immediate = 0, labelSymbol = elseLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
|
||||
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
|
||||
result += IRCodeChunk(afterIfLabel, null)
|
||||
} else {
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(result, IRInstruction(Opcode.BEQ, irDtLeft, reg1=tr.resultReg, immediate = 0, labelSymbol = afterIfLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
result += IRCodeChunk(afterIfLabel, null)
|
||||
}
|
||||
return result
|
||||
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)
|
||||
@@ -1201,35 +1301,35 @@ class IRCodeGen(
|
||||
val rightTr = expressionEval.translateExpression(condition.right)
|
||||
addToResult(result, rightTr, -1, rightTr.resultFpReg)
|
||||
val compResultReg = registers.nextFree()
|
||||
addInstr(
|
||||
result,
|
||||
IRInstruction(
|
||||
Opcode.FCOMP,
|
||||
IRDataType.FLOAT,
|
||||
reg1 = compResultReg,
|
||||
fpReg1 = leftTr.resultFpReg,
|
||||
fpReg2 = rightTr.resultFpReg
|
||||
),
|
||||
null
|
||||
)
|
||||
val elseBranch = when (condition.operator) {
|
||||
"==" -> Opcode.BNE
|
||||
"!=" -> Opcode.BEQ
|
||||
"<" -> Opcode.BGES
|
||||
">" -> Opcode.BLES
|
||||
"<=" -> Opcode.BGTS
|
||||
">=" -> Opcode.BLTS
|
||||
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1 = compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
|
||||
val elseBranch: Opcode
|
||||
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
|
||||
when (condition.operator) {
|
||||
"==" -> {
|
||||
elseBranch = Opcode.BSTNE
|
||||
useCmpi = true
|
||||
}
|
||||
"!=" -> {
|
||||
elseBranch = Opcode.BSTEQ
|
||||
useCmpi = true
|
||||
}
|
||||
"<" -> elseBranch = Opcode.BGES
|
||||
">" -> elseBranch = Opcode.BLES
|
||||
"<=" -> elseBranch = Opcode.BGTS
|
||||
">=" -> elseBranch = Opcode.BLTS
|
||||
else -> throw AssemblyError("weird operator")
|
||||
}
|
||||
if (ifElse.elseScope.children.isNotEmpty()) {
|
||||
// if and else parts
|
||||
val elseLabel = createLabelName()
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(
|
||||
result,
|
||||
IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = elseLabel),
|
||||
null
|
||||
)
|
||||
if(useCmpi) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
|
||||
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
|
||||
}
|
||||
} else
|
||||
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = elseLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
|
||||
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
|
||||
@@ -1237,11 +1337,13 @@ class IRCodeGen(
|
||||
} else {
|
||||
// only if part
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(
|
||||
result,
|
||||
IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel),
|
||||
null
|
||||
)
|
||||
if(useCmpi) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
|
||||
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
|
||||
}
|
||||
} else
|
||||
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
result += IRCodeChunk(afterIfLabel, null)
|
||||
}
|
||||
@@ -1252,26 +1354,34 @@ class IRCodeGen(
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
val number = (condition.right as? PtNumber)?.number?.toInt()
|
||||
if (number!=null) {
|
||||
elseBranchOpcode = when (condition.operator) {
|
||||
"==" -> Opcode.BNE
|
||||
"!=" -> Opcode.BEQ
|
||||
"<" -> if(signed) Opcode.BGES else Opcode.BGE
|
||||
">" -> if(signed) Opcode.BLES else Opcode.BLE
|
||||
"<=" -> if(signed) Opcode.BGTS else Opcode.BGT
|
||||
">=" -> if(signed) Opcode.BLTS else Opcode.BLT
|
||||
val elseBranch: Opcode
|
||||
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
|
||||
when (condition.operator) {
|
||||
"==" -> {
|
||||
elseBranch = Opcode.BSTNE
|
||||
useCmpi = true
|
||||
}
|
||||
"!=" -> {
|
||||
elseBranch = Opcode.BSTEQ
|
||||
useCmpi = true
|
||||
}
|
||||
"<" -> elseBranch = if(signed) Opcode.BGES else Opcode.BGE
|
||||
">" -> elseBranch = if(signed) Opcode.BLES else Opcode.BLE
|
||||
"<=" -> elseBranch = if(signed) Opcode.BGTS else Opcode.BGT
|
||||
">=" -> elseBranch = if(signed) Opcode.BLTS else Opcode.BLT
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
if (ifElse.elseScope.children.isNotEmpty()) {
|
||||
// if and else parts
|
||||
val elseLabel = createLabelName()
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(
|
||||
result, IRInstruction(
|
||||
elseBranchOpcode, branchDt,
|
||||
reg1 = leftTr.resultReg, immediate = number,
|
||||
labelSymbol = elseLabel
|
||||
), null
|
||||
)
|
||||
if(useCmpi) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
|
||||
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
|
||||
}
|
||||
} else
|
||||
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = elseLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
|
||||
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
|
||||
@@ -1279,52 +1389,56 @@ class IRCodeGen(
|
||||
} else {
|
||||
// only if part
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(
|
||||
result, IRInstruction(
|
||||
elseBranchOpcode, branchDt,
|
||||
reg1 = leftTr.resultReg, immediate = number,
|
||||
labelSymbol = afterIfLabel
|
||||
), null
|
||||
)
|
||||
if(useCmpi) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
|
||||
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
|
||||
}
|
||||
} else
|
||||
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = afterIfLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
result += IRCodeChunk(afterIfLabel, null)
|
||||
}
|
||||
} else {
|
||||
val rightTr = expressionEval.translateExpression(condition.right)
|
||||
val elseBranch: Opcode
|
||||
var useCmp = false
|
||||
addToResult(result, rightTr, rightTr.resultReg, -1)
|
||||
when (condition.operator) {
|
||||
"==" -> {
|
||||
elseBranchOpcode = Opcode.BNER
|
||||
useCmp = true
|
||||
elseBranch = Opcode.BSTNE
|
||||
elseBranchFirstReg = leftTr.resultReg
|
||||
elseBranchSecondReg = rightTr.resultReg
|
||||
}
|
||||
"!=" -> {
|
||||
elseBranchOpcode = Opcode.BEQR
|
||||
useCmp = true
|
||||
elseBranch = Opcode.BSTEQ
|
||||
elseBranchFirstReg = leftTr.resultReg
|
||||
elseBranchSecondReg = rightTr.resultReg
|
||||
}
|
||||
"<" -> {
|
||||
// else part when left >= right
|
||||
elseBranchOpcode = if (signed) Opcode.BGESR else Opcode.BGER
|
||||
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
|
||||
elseBranchFirstReg = leftTr.resultReg
|
||||
elseBranchSecondReg = rightTr.resultReg
|
||||
}
|
||||
">" -> {
|
||||
// else part when left <= right --> right >= left
|
||||
elseBranchOpcode = if (signed) Opcode.BGESR else Opcode.BGER
|
||||
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
|
||||
elseBranchFirstReg = rightTr.resultReg
|
||||
elseBranchSecondReg = leftTr.resultReg
|
||||
}
|
||||
"<=" -> {
|
||||
// else part when left > right
|
||||
elseBranchOpcode = if (signed) Opcode.BGTSR else Opcode.BGTR
|
||||
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
|
||||
elseBranchFirstReg = leftTr.resultReg
|
||||
elseBranchSecondReg = rightTr.resultReg
|
||||
}
|
||||
|
||||
">=" -> {
|
||||
// else part when left < right --> right > left
|
||||
elseBranchOpcode = if (signed) Opcode.BGTSR else Opcode.BGTR
|
||||
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
|
||||
elseBranchFirstReg = rightTr.resultReg
|
||||
elseBranchSecondReg = leftTr.resultReg
|
||||
}
|
||||
@@ -1334,13 +1448,14 @@ class IRCodeGen(
|
||||
// if and else parts
|
||||
val elseLabel = createLabelName()
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(
|
||||
result, IRInstruction(
|
||||
elseBranchOpcode, branchDt,
|
||||
reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg,
|
||||
labelSymbol = elseLabel
|
||||
), null
|
||||
)
|
||||
if(useCmp) {
|
||||
result += IRCodeChunk(null,null).also {
|
||||
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
|
||||
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
|
||||
}
|
||||
} else {
|
||||
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = elseLabel), null)
|
||||
}
|
||||
result += translateNode(ifElse.ifScope)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
|
||||
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
|
||||
@@ -1348,13 +1463,14 @@ class IRCodeGen(
|
||||
} else {
|
||||
// only if part
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(
|
||||
result, IRInstruction(
|
||||
elseBranchOpcode, branchDt,
|
||||
reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg,
|
||||
labelSymbol = afterIfLabel
|
||||
), null
|
||||
)
|
||||
if(useCmp) {
|
||||
result += IRCodeChunk(null,null).also {
|
||||
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
|
||||
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
|
||||
}
|
||||
} else {
|
||||
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = afterIfLabel), null)
|
||||
}
|
||||
result += translateNode(ifElse.ifScope)
|
||||
result += IRCodeChunk(afterIfLabel, null)
|
||||
}
|
||||
@@ -1525,12 +1641,15 @@ class IRCodeGen(
|
||||
addToResult(result, countTr, countTr.resultReg, -1)
|
||||
if(constIntValue(repeat.count)==null) {
|
||||
// check if the counter is already zero
|
||||
addInstr(result, IRInstruction(Opcode.BEQ, irDt, reg1=countTr.resultReg, immediate = 0, labelSymbol = skipRepeatLabel), null)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, irDt, reg1=countTr.resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel)
|
||||
}
|
||||
}
|
||||
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg)
|
||||
it += IRInstruction(Opcode.BNE, irDt, reg1 = countTr.resultReg, immediate = 0, labelSymbol = repeatLabel)
|
||||
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
|
||||
}
|
||||
result += IRCodeChunk(skipRepeatLabel, null)
|
||||
return result
|
||||
|
||||
@@ -45,6 +45,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
val indexedInstructions = chunk1.instructions.withIndex()
|
||||
.map { IndexedValue(it.index, it.value) }
|
||||
val changed = removeNops(chunk1, indexedInstructions)
|
||||
|| replaceConcatZeroMsbWithExt(chunk1, indexedInstructions)
|
||||
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|
||||
|| removeUselessArithmetic(chunk1, indexedInstructions)
|
||||
|| removeNeedlessCompares(chunk1, indexedInstructions)
|
||||
@@ -63,6 +64,22 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
irprog.linkChunks() // re-link
|
||||
}
|
||||
|
||||
private fun replaceConcatZeroMsbWithExt(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if (ins.opcode == Opcode.CONCAT) {
|
||||
// if the previous instruction loads a zero in the msb, this can be turned into EXT.B instead
|
||||
val prev = indexedInstructions[idx-1].value
|
||||
if(prev.opcode==Opcode.LOAD && prev.immediate==0 && prev.reg1==ins.reg2) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = ins.reg1, reg2 = ins.reg3)
|
||||
chunk.instructions.removeAt(idx-1)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeEmptyChunks(sub: IRSubroutine) {
|
||||
if(sub.chunks.isEmpty())
|
||||
return
|
||||
|
||||
@@ -25,18 +25,29 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
|
||||
private fun pruneSymboltable(blockLabel: String) {
|
||||
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction
|
||||
// we could clean up the SymbolTable as well, but ONLY if these symbols aren't referenced somewhere still in an instruction or variable initializer value
|
||||
val prefix = "$blockLabel."
|
||||
val blockVars = irprog.st.allVariables().filter { it.name.startsWith(prefix) }
|
||||
blockVars.forEach { stVar ->
|
||||
irprog. allSubs().flatMap { it.chunks }.forEach { chunk ->
|
||||
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
|
||||
chunk.instructions.forEach { ins ->
|
||||
if(ins.labelSymbol == stVar.name) {
|
||||
return
|
||||
return // symbol occurs in an instruction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
irprog.st.allVariables().forEach { stVar->
|
||||
val initValue = stVar.onetimeInitializationArrayValue
|
||||
if(initValue!=null && !initValue.isEmpty()) {
|
||||
if(initValue.any {
|
||||
it.addressOfSymbol?.startsWith(blockLabel)==true
|
||||
})
|
||||
return // symbol occurs in an initializer value (address-of this symbol)_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
irprog.st.removeTree(blockLabel)
|
||||
}
|
||||
|
||||
@@ -207,9 +218,7 @@ class IRUnusedCodeRemover(
|
||||
return removeUnlinkedChunks(linkedChunks)
|
||||
}
|
||||
|
||||
private fun removeUnlinkedChunks(
|
||||
linkedChunks: Set<IRCodeChunkBase>
|
||||
): Int {
|
||||
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
|
||||
var numRemoved = 0
|
||||
irprog.foreachSub { sub ->
|
||||
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||
|
||||
@@ -18,6 +18,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
emptyList(),
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
compTarget = target,
|
||||
|
||||
@@ -19,6 +19,7 @@ class TestVmCodeGen: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
zpReserved = emptyList(),
|
||||
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = target,
|
||||
@@ -41,7 +42,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
@@ -91,7 +92,7 @@ class TestVmCodeGen: FunSpec({
|
||||
program.add(block)
|
||||
|
||||
// define the "cx16.r0" virtual register
|
||||
val cx16block = PtBlock("cx16", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val cx16block = PtBlock("cx16", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||
program.add(cx16block)
|
||||
|
||||
@@ -120,7 +121,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -183,7 +184,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -242,7 +243,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("f1", DataType.FLOAT, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -289,7 +290,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -352,7 +353,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("sb1", DataType.BYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -411,7 +412,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("ub1", DataType.UBYTE, ZeropageWish.DONTCARE, null, null, Position.DUMMY))
|
||||
val if1 = PtIfElse(Position.DUMMY)
|
||||
@@ -451,7 +452,7 @@ class TestVmCodeGen: FunSpec({
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val block = PtBlock("main", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||
block.add(romsub)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
@@ -466,7 +467,11 @@ class TestVmCodeGen: FunSpec({
|
||||
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||
irChunks.size shouldBe 1
|
||||
val callInstr = irChunks.single().instructions.single()
|
||||
irChunks[0].instructions.size shouldBe 2
|
||||
val preparecallInstr = irChunks[0].instructions[0]
|
||||
preparecallInstr.opcode shouldBe Opcode.PREPARECALL
|
||||
preparecallInstr.immediate shouldBe 0
|
||||
val callInstr = irChunks[0].instructions[1]
|
||||
callInstr.opcode shouldBe Opcode.CALL
|
||||
callInstr.address shouldBe 0x5000
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@ dependencies {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,13 @@ import kotlin.math.floor
|
||||
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// @( &thing ) --> thing
|
||||
// @( &thing ) --> thing (but only if thing is a byte type!)
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
return if(addrOf!=null)
|
||||
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
|
||||
else
|
||||
noModifications
|
||||
if(addrOf!=null) {
|
||||
if(addrOf.identifier.inferType(program).isBytes)
|
||||
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
@@ -86,7 +87,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
|
||||
if(expr.operator=="==" && rightconst!=null) {
|
||||
val leftExpr = expr.left as? BinaryExpression
|
||||
if(leftExpr!=null) {
|
||||
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
|
||||
if(leftExpr?.constValue(program) != null) {
|
||||
val leftRightConst = leftExpr.right.constValue(program)
|
||||
if(leftRightConst!=null) {
|
||||
when (leftExpr.operator) {
|
||||
|
||||
@@ -251,7 +251,16 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
|
||||
if(decl.isArray && decl.type==VarDeclType.MEMORY) {
|
||||
val memaddr = decl.value?.constValue(program)
|
||||
if(memaddr!=null && memaddr !== decl.value) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.value = it }, memaddr, decl
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
if(decl.isArray){
|
||||
val arraysize = decl.arraysize
|
||||
if(arraysize==null) {
|
||||
@@ -276,107 +285,10 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange?.isEmpty()==true) {
|
||||
if(constRange.first>constRange.last && constRange.step>=0)
|
||||
errors.err("descending range with positive step", decl.value?.position!!)
|
||||
else if(constRange.first<constRange.last && constRange.step<=0)
|
||||
errors.err("ascending range with negative step", decl.value?.position!!)
|
||||
}
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = numericLv.number
|
||||
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_BOOL -> {
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if(numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
val replacedArrayInitializer = createConstArrayInitializerValue(decl)
|
||||
if(replacedArrayInitializer!=null)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, replacedArrayInitializer, decl))
|
||||
}
|
||||
else -> {
|
||||
// nothing to do for this type
|
||||
@@ -386,4 +298,112 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
|
||||
|
||||
if(decl.type==VarDeclType.MEMORY)
|
||||
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
|
||||
|
||||
// convert the initializer range expression from a range or int, to an actual array.
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange?.isEmpty()==true) {
|
||||
if(constRange.first>constRange.last && constRange.step>=0)
|
||||
errors.err("descending range with positive step", decl.value?.position!!)
|
||||
else if(constRange.first<constRange.last && constRange.step<=0)
|
||||
errors.err("ascending range with negative step", decl.value?.position!!)
|
||||
}
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
return if(eltType in ByteDatatypes) {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return null
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
}
|
||||
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return null
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = numericLv.number
|
||||
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_BOOL -> {
|
||||
val numericLv = decl.value as? NumericLiteral
|
||||
val size = decl.arraysize?.constIndex() ?: return null
|
||||
if(numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = if(numericLv.number==0.0) 0.0 else 1.0
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.UBYTE, fillvalue, numericLv.position) }.toTypedArray<Expression>()
|
||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
|
||||
}
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine.inline && subroutine.statements.size>1) {
|
||||
require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))
|
||||
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visit(subroutine)
|
||||
|
||||
@@ -30,7 +30,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
|
||||
val arg = functionCallStatement.args.single()
|
||||
val stringVar: IdentifierReference? = if(arg is AddressOf) {
|
||||
arg.identifier
|
||||
if(arg.arrayIndex==null) arg.identifier else null
|
||||
} else {
|
||||
arg as? IdentifierReference
|
||||
}
|
||||
@@ -430,6 +430,27 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val constantValue = whenStmt.condition.constValue(program)?.number
|
||||
if(constantValue!=null) {
|
||||
// when condition is a constant
|
||||
var matchingChoice: WhenChoice? = null
|
||||
loop@ for(choice in whenStmt.choices) {
|
||||
for(value in choice.values ?: emptyList()) {
|
||||
if(value.constValue(program)?.number == constantValue) {
|
||||
matchingChoice = choice
|
||||
break@loop
|
||||
}
|
||||
}
|
||||
}
|
||||
if(matchingChoice==null)
|
||||
matchingChoice = whenStmt.choices.singleOrNull { it.values==null }
|
||||
if(matchingChoice!=null) {
|
||||
// get rid of the whole when-statement and just leave the matching choice
|
||||
return listOf(IAstModification.ReplaceNode(whenStmt, matchingChoice.statements, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -33,14 +33,14 @@ dependencies {
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation project(':virtualmachine')
|
||||
implementation "org.antlr:antlr4-runtime:4.12.0"
|
||||
implementation "org.antlr:antlr4-runtime:4.13.1"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
|
||||
|
||||
testImplementation project(':intermediate')
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.6.2'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
@@ -59,10 +59,10 @@ configurations {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
srcDir "${project.projectDir}/src"
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
srcDir "${project.projectDir}/res"
|
||||
}
|
||||
}
|
||||
test {
|
||||
|
||||
@@ -198,6 +198,14 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
sub disable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub enable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
; TODO
|
||||
|
||||
@@ -24,12 +24,24 @@ sub spc() {
|
||||
txt.chrout(' ')
|
||||
}
|
||||
|
||||
asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
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
|
||||
; TODO
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
pokew(COLCRS, col as uword)
|
||||
}
|
||||
|
||||
sub get_column() -> ubyte {
|
||||
return peekw(COLCRS) as ubyte
|
||||
}
|
||||
|
||||
sub row(ubyte rownum) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
@(ROWCRS) = rownum
|
||||
}
|
||||
|
||||
sub get_row() -> ubyte {
|
||||
return @(ROWCRS)
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
|
||||
@@ -372,12 +384,14 @@ sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) {
|
||||
; ---- set cursor at specific position
|
||||
; TODO
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
sub plot(ubyte col, ubyte rownum) {
|
||||
column(col)
|
||||
row(rownum)
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
@(colptr) = get_column()
|
||||
@(rowptr) = get_row()
|
||||
}
|
||||
|
||||
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
%import syslib
|
||||
%import textio
|
||||
|
||||
; Bitmap pixel graphics module for the Commodore 128
|
||||
|
||||
; TODO c128 actually implement the graphics routines. Ideally a way to 'borrow' the code form the C64 version without just copy-pasting that here?
|
||||
|
||||
graphics {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 200
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub disable_bitmap_mode() {
|
||||
; enables text mode, erase the text screen, color white
|
||||
; TODO
|
||||
}
|
||||
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub fillrect(uword x, uword y, uword width, uword height) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub rect(uword x, uword y, uword width, uword height) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub horizontal_line(uword x, uword y, uword length) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub vertical_line(uword x, uword y, uword height) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
|
||||
%asm {{
|
||||
nop ; TODO
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -734,6 +734,20 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub disable_caseswitch() {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 247
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub enable_caseswitch() {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 247
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-128
|
||||
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
; these work with PETSCII encoding instead.
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -38,6 +40,45 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tax
|
||||
clc
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_row() -> ubyte @X {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tya
|
||||
ldy #$00
|
||||
sta (colptr),y
|
||||
txa
|
||||
sta (rowptr),y
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
; (assumes screen and color matrix are at their default addresses)
|
||||
@@ -238,10 +279,10 @@ _scroll_screen ; scroll only the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; ---- print null terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
@@ -427,7 +468,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
@@ -584,4 +626,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
; bitmap pixel graphics module for the C64
|
||||
; only black/white monochrome 320x200 for now
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
;
|
||||
; Sets character matrix and bitmap screen memory at a higher memory location $5c00-$7fff
|
||||
; so that the program itself can be larger without starting to overwrite the graphics memory.
|
||||
|
||||
@@ -244,7 +247,8 @@ hline_zero2
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp ploty
|
||||
@@ -273,16 +277,17 @@ hline_zero2
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
@@ -295,12 +300,12 @@ hline_zero2
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -737,6 +737,20 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub disable_caseswitch() {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 657
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub enable_caseswitch() {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 657
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
|
||||
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
; these work with PETSCII encoding instead.
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -39,6 +41,45 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tax
|
||||
clc
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_row() -> ubyte @X {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tya
|
||||
ldy #$00
|
||||
sta (colptr),y
|
||||
txa
|
||||
sta (rowptr),y
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
; (assumes screen and color matrix are at their default addresses)
|
||||
@@ -237,10 +278,10 @@ _scroll_screen ; scroll only the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; ---- print null terminated string, in PESCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
@@ -426,7 +467,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
@@ -583,4 +625,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,16 @@
|
||||
; Commander X16 disk drive I/O routines.
|
||||
; Largely compatible with the default C64 ones, but adds more stuff specific to the X16 as well.
|
||||
|
||||
; NOTE: If you experience weird behavior with these routines and you are using them
|
||||
; in the X16 emulator using HostFs, please try again with an SD-card image instead first.
|
||||
; It is possible that there are still small differences between HostFS and actual CBM DOS in the emulator.
|
||||
;
|
||||
; About the secondary addresses:
|
||||
; for writes (new files or overwrites), you can use 1 (without the mode string) or 2-14 (with the mode string)
|
||||
; for reads (existing files) you can use 0 or 2-14 (mode string is optional)
|
||||
; for modify mode (new files* or existing files), you must use 2-14, and the mode string ,s,m is required
|
||||
|
||||
|
||||
%import textio
|
||||
%import string
|
||||
%import syslib
|
||||
@@ -8,22 +18,33 @@
|
||||
diskio {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte READ_IO_CHANNEL=12
|
||||
const ubyte WRITE_IO_CHANNEL=13
|
||||
|
||||
ubyte drivenumber = 8
|
||||
|
||||
sub set_drive(ubyte number) {
|
||||
drivenumber = number
|
||||
}
|
||||
|
||||
sub reset_read_channel() {
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
}
|
||||
|
||||
sub reset_write_channel() {
|
||||
cbm.CHKOUT(WRITE_IO_CHANNEL)
|
||||
}
|
||||
|
||||
sub directory() -> bool {
|
||||
; -- Prints the directory contents to the screen. Returns success.
|
||||
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(12, drivenumber, 0)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(12) ; use #12 as input channel
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@@ -61,7 +82,7 @@ diskio {
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(12)
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
|
||||
if status and status & $40 == 0 { ; bit 6=end of file
|
||||
txt.print("\ni/o error, status: ")
|
||||
@@ -76,12 +97,12 @@ io_error:
|
||||
sub diskname() -> uword {
|
||||
; returns disk label name or 0 if error
|
||||
cbm.SETNAM(3, "$")
|
||||
cbm.SETLFS(12, diskio.drivenumber, 0)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void cbm.OPEN() ; open 12,8,0,"$=c"
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(12) ; use #12 as input channel
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@@ -89,12 +110,12 @@ io_error:
|
||||
; skip up to entry name
|
||||
}
|
||||
|
||||
cx16.r0 = &diskio.list_filename
|
||||
cx16.r0 = &list_filename
|
||||
repeat {
|
||||
@(cx16.r0) = cbm.CHRIN()
|
||||
if @(cx16.r0)=='"' {
|
||||
@(cx16.r0) = ' '
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&list_filename {
|
||||
@(cx16.r0) = 0
|
||||
cx16.r0--
|
||||
}
|
||||
@@ -106,10 +127,10 @@ io_error:
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(12)
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
if status and status & $40 == 0
|
||||
return 0
|
||||
return diskio.list_filename
|
||||
return list_filename
|
||||
}
|
||||
|
||||
; internal variables for the iterative file lister / loader
|
||||
@@ -124,7 +145,7 @@ io_error:
|
||||
|
||||
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
|
||||
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
|
||||
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
|
||||
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
|
||||
; After the last filename one additional 0 byte is placed to indicate the end of the list.
|
||||
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
|
||||
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
|
||||
@@ -150,7 +171,7 @@ io_error:
|
||||
return files_found
|
||||
}
|
||||
|
||||
; ----- iterative file lister functions (uses io channel 12) -----
|
||||
; ----- iterative file lister functions (uses the read io channel) -----
|
||||
|
||||
sub lf_start_list(uword pattern_ptr) -> bool {
|
||||
; -- start an iterative file listing with optional pattern matching.
|
||||
@@ -161,11 +182,11 @@ io_error:
|
||||
iteration_in_progress = true
|
||||
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(12, drivenumber, 0)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(12) ; use #12 as input channel
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@@ -190,7 +211,7 @@ io_error:
|
||||
return false
|
||||
|
||||
repeat {
|
||||
void cbm.CHKIN(12) ; use #12 as input channel again
|
||||
reset_read_channel() ; use the input io channel again
|
||||
|
||||
uword nameptr = &list_filename
|
||||
ubyte blocks_lsb = cbm.CHRIN()
|
||||
@@ -251,32 +272,34 @@ close_end:
|
||||
; -- end an iterative file listing session (close channels).
|
||||
if iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(12)
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file loader functions (uses io channel 12) -----
|
||||
; ----- iterative file loader functions (uses the read io channel) -----
|
||||
|
||||
sub f_open(uword filenameptr) -> bool {
|
||||
sub f_open(str 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() !
|
||||
f_close()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL) ; note: has to be Channel,x,Channel because otherwise f_seek doesn't work
|
||||
void cbm.OPEN() ; open 12,8,12,"filename"
|
||||
if_cc {
|
||||
if cbm.READST()==0 {
|
||||
iteration_in_progress = true
|
||||
void cbm.CHKIN(12) ; use #12 as input channel
|
||||
reset_read_channel()
|
||||
if_cc {
|
||||
void cbm.CHRIN() ; read first byte to test for file not found
|
||||
if not cbm.READST() {
|
||||
cbm.CLOSE(12) ; close file because we already consumed first byte
|
||||
cbm.CLOSE(READ_IO_CHANNEL) ; close file because we already consumed first byte
|
||||
void cbm.OPEN() ; re-open the file
|
||||
void cbm.CHKIN(12)
|
||||
cbm.CLRCHN() ; reset default i/o channels
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -290,21 +313,21 @@ close_end:
|
||||
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
|
||||
; -- read from the currently open file, up to the given number of bytes.
|
||||
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.macptr() yourself with the vera data register as address.
|
||||
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
|
||||
if not iteration_in_progress or not num_bytes
|
||||
return 0
|
||||
|
||||
reset_read_channel()
|
||||
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||
|
||||
; commander X16 supports fast block-read via macptr() kernal call
|
||||
uword readsize
|
||||
while num_bytes {
|
||||
readsize = 255
|
||||
if num_bytes<readsize
|
||||
readsize = num_bytes
|
||||
readsize = cx16.macptr(lsb(readsize), bufferpointer, false)
|
||||
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
|
||||
if_cs
|
||||
goto byte_read_loop ; macptr block read not supported, do fallback loop
|
||||
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
|
||||
list_blocks += readsize
|
||||
bufferpointer += readsize
|
||||
if msb(bufferpointer) == $c0
|
||||
@@ -317,7 +340,7 @@ close_end:
|
||||
}
|
||||
return list_blocks ; number of bytes read
|
||||
|
||||
byte_read_loop: ; fallback if macptr() isn't supported on the device
|
||||
byte_read_loop: ; fallback if MACPTR isn't supported on the device
|
||||
%asm {{
|
||||
lda bufferpointer
|
||||
sta m_in_buffer+1
|
||||
@@ -348,10 +371,11 @@ m_in_buffer sta $ffff
|
||||
; optimized for Commander X16 to use MACPTR block read kernal call
|
||||
sub f_read_all(uword bufferpointer) -> uword {
|
||||
; -- read the full contents of the file, returns number of bytes read.
|
||||
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.macptr() yourself with the vera data register as address.
|
||||
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
|
||||
if not iteration_in_progress
|
||||
return 0
|
||||
|
||||
reset_read_channel()
|
||||
uword total_read = 0
|
||||
while not cbm.READST() {
|
||||
cx16.r0 = f_read(bufferpointer, 256)
|
||||
@@ -370,8 +394,7 @@ m_in_buffer sta $ffff
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx #12
|
||||
jsr cbm.CHKIN ; use channel 12 again for input
|
||||
jsr reset_read_channel
|
||||
ldy #0
|
||||
_loop jsr cbm.CHRIN
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
@@ -392,23 +415,41 @@ _end rts
|
||||
; -- end an iterative file loading session (close channels).
|
||||
if iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(12)
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file writing functions (uses io channel 13) -----
|
||||
; ----- iterative file writing functions (uses write io channel) -----
|
||||
|
||||
sub f_open_w(uword filenameptr) -> bool {
|
||||
; -- open a file for iterative writing with f_write
|
||||
sub f_open_w(str filename) -> 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!
|
||||
return internal_f_open_w(filename, false)
|
||||
}
|
||||
|
||||
sub f_open_w_seek(str filename) -> bool {
|
||||
; -- Open an existing file for iterative writing with f_write, and seeking with f_seek_w.
|
||||
return internal_f_open_w(filename, true)
|
||||
}
|
||||
|
||||
sub internal_f_open_w(str filename, bool open_for_seeks) -> bool {
|
||||
f_close_w()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(13, drivenumber, 1)
|
||||
void cbm.OPEN() ; open 13,8,1,"filename"
|
||||
list_filename = filename
|
||||
str modifier = ",s,?"
|
||||
modifier[3] = 'w'
|
||||
if open_for_seeks
|
||||
modifier[3] = 'm'
|
||||
cx16.r0L = string.append(list_filename, modifier) ; secondary 13 requires a mode suffix to signal we're writing/modifying
|
||||
cbm.SETNAM(cx16.r0L, list_filename)
|
||||
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, WRITE_IO_CHANNEL)
|
||||
void cbm.OPEN() ; open 13,8,13,"filename"
|
||||
if_cc {
|
||||
cbm.CHKOUT(13) ; use #13 as output channel
|
||||
return not cbm.READST()
|
||||
}
|
||||
f_close_w()
|
||||
@@ -418,7 +459,22 @@ _end rts
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||
; -- write the given number of bytes to the currently open file
|
||||
if num_bytes!=0 {
|
||||
cbm.CHKOUT(13) ; use #13 as output channel again
|
||||
reset_write_channel()
|
||||
do {
|
||||
cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
|
||||
if_cs
|
||||
goto no_mciout
|
||||
num_bytes -= cx16.r0
|
||||
bufferpointer += cx16.r0
|
||||
if msb(bufferpointer) == $c0
|
||||
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
|
||||
if cbm.READST()!=0
|
||||
return false
|
||||
} until num_bytes==0
|
||||
return not cbm.READST()
|
||||
|
||||
no_mciout:
|
||||
; the device doesn't support MCIOUT, use a normal per-byte write loop
|
||||
repeat num_bytes {
|
||||
cbm.CHROUT(@(bufferpointer))
|
||||
bufferpointer++
|
||||
@@ -431,7 +487,7 @@ _end rts
|
||||
sub f_close_w() {
|
||||
; -- end an iterative file writing session (close channels).
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(13)
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
}
|
||||
|
||||
|
||||
@@ -506,9 +562,6 @@ io_error:
|
||||
if_cc
|
||||
cx16.r0L = cbm.READST()==0
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
@@ -557,8 +610,6 @@ io_error:
|
||||
+
|
||||
}}
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
return cx16.r1
|
||||
}
|
||||
|
||||
@@ -693,11 +744,11 @@ internal_vload:
|
||||
cx16.r12 = reversebuffer + MAX_PATH_LEN-1
|
||||
@(cx16.r12)=0
|
||||
cbm.SETNAM(3, "$=c")
|
||||
cbm.SETLFS(12, diskio.drivenumber, 0)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
void cbm.OPEN() ; open 12,8,0,"$=c"
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(12) ; use #12 as input channel
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@@ -734,7 +785,7 @@ internal_vload:
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(12)
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
if status and status & $40 == 0
|
||||
return 0
|
||||
if @(cx16.r12)==0 {
|
||||
@@ -767,7 +818,7 @@ io_error:
|
||||
sub f_seek(uword pos_hiword, uword pos_loword) {
|
||||
; -- seek in the reading file opened with f_open, to the given 32-bits position
|
||||
ubyte[6] command = ['p',0,0,0,0,0]
|
||||
command[1] = 12 ; f_open uses channel 12
|
||||
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
|
||||
command[2] = lsb(pos_loword)
|
||||
command[3] = msb(pos_loword)
|
||||
command[4] = lsb(pos_hiword)
|
||||
@@ -777,19 +828,22 @@ io_error:
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLOSE(15)
|
||||
void cbm.CHKIN(12) ; back to the channel that f_open uses
|
||||
reset_read_channel() ; back to the read io channel
|
||||
}
|
||||
|
||||
|
||||
; NOTE: f_seek_w() doesn't work reliably right now. I only manage to corrupt the fat32 filesystem on the sdcard with it...
|
||||
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
|
||||
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
|
||||
; diskio.f_seek.command[1] = 13 ; f_open_w uses channel 13
|
||||
; diskio.f_seek.command[2] = lsb(pos_loword)
|
||||
; diskio.f_seek.command[3] = msb(pos_loword)
|
||||
; diskio.f_seek.command[4] = lsb(pos_hiword)
|
||||
; diskio.f_seek.command[5] = msb(pos_hiword)
|
||||
; goto diskio.f_seek.send_command
|
||||
; }
|
||||
sub f_seek_w(uword pos_hiword, uword pos_loword) {
|
||||
; -- seek in the output file opened with f_open_w_seek, to the given 32-bits position
|
||||
diskio.f_seek.command[1] = WRITE_IO_CHANNEL ; f_open_w uses this secondary address
|
||||
diskio.f_seek.command[2] = lsb(pos_loword)
|
||||
diskio.f_seek.command[3] = msb(pos_loword)
|
||||
diskio.f_seek.command[4] = lsb(pos_hiword)
|
||||
diskio.f_seek.command[5] = msb(pos_hiword)
|
||||
cbm.SETNAM(sizeof(diskio.f_seek.command), &diskio.f_seek.command)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLOSE(15)
|
||||
reset_write_channel() ; back to the write io channel
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
; Emulator debug interface.
|
||||
; Docs: https://github.com/X16Community/x16-emulator#emulator-io-registers
|
||||
|
||||
emudbg {
|
||||
|
||||
const uword EMU_BASE = $9fb0
|
||||
|
||||
&ubyte EMU_DBG_HOTKEY_ENABLED = EMU_BASE + 0
|
||||
&ubyte EMU_LOG_VIDEO = EMU_BASE + 1
|
||||
&ubyte EMU_LOG_KEYBOARD = EMU_BASE + 2
|
||||
&ubyte EMU_ECHO_MODE = EMU_BASE + 3
|
||||
&ubyte EMU_SAVE_ON_EXIT = EMU_BASE + 4
|
||||
&ubyte EMU_RECORD_GIF = EMU_BASE + 5
|
||||
&ubyte EMU_RECORD_WAV = EMU_BASE + 6
|
||||
&ubyte EMU_CMDKEYS_DISABLED = EMU_BASE + 7
|
||||
&ubyte EMU_CPUCLK_L = EMU_BASE + 8 ; write: reset cpu clock to 0
|
||||
&ubyte EMU_CPUCLK_M = EMU_BASE + 9 ; write: outputs "User debug 1: $xx"
|
||||
&ubyte EMU_CPUCLK_H = EMU_BASE + 10 ; write: outputs "User debug 2: $xx"
|
||||
&ubyte EMU_CPUCLK_U = EMU_BASE + 11 ; write: outputs as character to console
|
||||
; 12 is unused for now
|
||||
&ubyte EMU_KEYMAP = EMU_BASE + 13
|
||||
&ubyte EMU_EMU_DETECT1 = EMU_BASE + 14
|
||||
&ubyte EMU_EMU_DETECT2 = EMU_BASE + 15
|
||||
|
||||
sub is_emulator() -> bool {
|
||||
; Test for emulator presence.
|
||||
; It is recommended to only use the other debug routines if this returns true.
|
||||
return EMU_EMU_DETECT1=='1' and EMU_EMU_DETECT2=='6'
|
||||
}
|
||||
|
||||
asmsub console_write(str isoString @R0) clobbers(Y) {
|
||||
; note: make sure the text is in Iso encoding.
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda (cx16.r0),y
|
||||
beq +
|
||||
sta p8_EMU_CPUCLK_U
|
||||
iny
|
||||
bne -
|
||||
+
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub console_chrout(ubyte char @A) {
|
||||
; note: make sure the character is in Iso encoding.
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_U
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub console_value1(ubyte value @A) {
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_M
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub console_value2(ubyte value @A) {
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_H
|
||||
}}
|
||||
}
|
||||
}
|
||||
+223
-499
@@ -4,21 +4,23 @@
|
||||
;
|
||||
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
|
||||
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
|
||||
; Note: for identical routines for a monochrome 1 bpp screen, use the "monogfx" module instead.
|
||||
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
|
||||
; Note: this library implements code for various resolutions and color depths. This takes up memory.
|
||||
; If you're memory constrained you should probably not use this built-in library,
|
||||
; but make a copy in your project only containing the code for the required resolution.
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
;
|
||||
;
|
||||
; SCREEN MODE LIST:
|
||||
; mode 0 = reset back to default text mode
|
||||
; mode 1 = bitmap 320 x 240 monochrome
|
||||
; mode 2 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; mode 3 = bitmap 320 x 240 x 16c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; mode 4 = bitmap 320 x 240 x 256c (like SCREEN $80 but using this api instead of kernal)
|
||||
; mode 5 = bitmap 640 x 480 monochrome
|
||||
; mode 6 = bitmap 640 x 480 x 4c
|
||||
; higher color dephts in highres are not supported due to lack of VRAM
|
||||
; mode 1 = bitmap 320 x 240 x 256c (8 bpp)
|
||||
; mode 2 = bitmap 640 x 480 x 4c (2 bpp)
|
||||
; mode 3 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; mode 4 = bitmap 320 x 240 x 16c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; mode 5 = bitmap 640 x 400 x 16c (not yet implemented)
|
||||
|
||||
gfx2 {
|
||||
|
||||
@@ -29,24 +31,11 @@ gfx2 {
|
||||
uword width = 0
|
||||
uword height = 0
|
||||
ubyte bpp = 0
|
||||
bool monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
|
||||
|
||||
sub screen_mode(ubyte mode) {
|
||||
cx16.VERA_CTRL=0
|
||||
when mode {
|
||||
1 -> {
|
||||
; lores monochrome
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 64
|
||||
cx16.VERA_DC_VSCALE = 64
|
||||
cx16.VERA_L1_CONFIG = %00000100
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
width = 320
|
||||
height = 240
|
||||
bpp = 1
|
||||
}
|
||||
; TODO modes 2, 3
|
||||
4 -> {
|
||||
; lores 256c
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 64
|
||||
@@ -54,23 +43,8 @@ gfx2 {
|
||||
cx16.VERA_L1_CONFIG = %00000111
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
width = 320
|
||||
height = 240
|
||||
bpp = 8
|
||||
}
|
||||
5 -> {
|
||||
; highres monochrome
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 128
|
||||
cx16.VERA_DC_VSCALE = 128
|
||||
cx16.VERA_L1_CONFIG = %00000100
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = %00000001
|
||||
width = 640
|
||||
height = 480
|
||||
bpp = 1
|
||||
}
|
||||
6 -> {
|
||||
2 -> {
|
||||
; highres 4c
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 128
|
||||
@@ -78,61 +52,63 @@ gfx2 {
|
||||
cx16.VERA_L1_CONFIG = %00000101
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = %00000001
|
||||
width = 640
|
||||
height = 480
|
||||
bpp = 2
|
||||
}
|
||||
else -> {
|
||||
; back to default text mode
|
||||
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||
cbm.CINT()
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||
}
|
||||
}
|
||||
|
||||
init_mode(mode)
|
||||
if active_mode
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub init_mode(ubyte mode) {
|
||||
; set the internal configuration variables corresponding to the given screenmode
|
||||
; doesn't manipulate Vera / the actual display mode
|
||||
active_mode = mode
|
||||
when mode {
|
||||
1 -> {
|
||||
width = 320
|
||||
height = 240
|
||||
bpp = 8
|
||||
}
|
||||
2 -> {
|
||||
width = 640
|
||||
height = 480
|
||||
bpp = 2
|
||||
}
|
||||
else -> {
|
||||
width = 0
|
||||
height = 0
|
||||
bpp = 0
|
||||
mode = 0
|
||||
active_mode = 0
|
||||
}
|
||||
}
|
||||
|
||||
active_mode = mode
|
||||
if bpp
|
||||
clear_screen()
|
||||
}
|
||||
|
||||
sub clear_screen() {
|
||||
monochrome_stipple(false)
|
||||
sub clear_screen(ubyte color) {
|
||||
position(0, 0)
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores monochrome
|
||||
repeat 240/2/8
|
||||
cs_innerloop640()
|
||||
}
|
||||
; TODO modes 2, 3
|
||||
4 -> {
|
||||
; lores 256c
|
||||
repeat 240/2
|
||||
cs_innerloop640()
|
||||
cs_innerloop640(color)
|
||||
}
|
||||
5 -> {
|
||||
; highres monochrome
|
||||
repeat 480/8
|
||||
cs_innerloop640()
|
||||
}
|
||||
6 -> {
|
||||
2 -> {
|
||||
; highres 4c
|
||||
ubyte[] colors = [%00000000, %01010101, %10101010, %11111111]
|
||||
color = colors[color&3]
|
||||
repeat 480/4
|
||||
cs_innerloop640()
|
||||
cs_innerloop640(color)
|
||||
}
|
||||
; modes 7 and 8 not supported due to lack of VRAM
|
||||
}
|
||||
position(0, 0)
|
||||
}
|
||||
|
||||
sub monochrome_stipple(bool enable) {
|
||||
monochrome_dont_stipple_flag = not enable
|
||||
}
|
||||
|
||||
sub rect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
|
||||
if rwidth==0 or rheight==0
|
||||
return
|
||||
@@ -147,6 +123,8 @@ gfx2 {
|
||||
}
|
||||
|
||||
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, ubyte color) {
|
||||
; Draw a filled rectangle of the given size and color.
|
||||
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
||||
if rwidth==0
|
||||
return
|
||||
repeat rheight {
|
||||
@@ -156,87 +134,10 @@ gfx2 {
|
||||
}
|
||||
|
||||
sub horizontal_line(uword xx, uword yy, uword length, ubyte color) {
|
||||
ubyte[9] masked_starts = [ 0, %00000001, %00000011, %00000111, %00001111, %00011111, %00111111, %01111111, %11111111]
|
||||
ubyte[9] masked_ends = [ 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110, %11111111]
|
||||
|
||||
if length==0
|
||||
return
|
||||
when active_mode {
|
||||
1, 5 -> {
|
||||
; monochrome modes, either resolution
|
||||
ubyte separate_pixels = (8-lsb(xx)) & 7
|
||||
if separate_pixels as uword > length
|
||||
separate_pixels = lsb(length)
|
||||
if separate_pixels {
|
||||
if monochrome_dont_stipple_flag {
|
||||
position(xx,yy)
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
|
||||
if color
|
||||
cx16.VERA_DATA0 |= masked_starts[separate_pixels]
|
||||
else
|
||||
cx16.VERA_DATA0 &= ~masked_starts[separate_pixels]
|
||||
xx += separate_pixels
|
||||
} else {
|
||||
repeat separate_pixels {
|
||||
plot(xx, yy, color)
|
||||
xx++
|
||||
}
|
||||
}
|
||||
length -= separate_pixels
|
||||
}
|
||||
if length {
|
||||
position(xx, yy)
|
||||
separate_pixels = lsb(length) & 7
|
||||
xx += length & $fff8
|
||||
%asm {{
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lda color
|
||||
bne +
|
||||
ldy #0 ; black
|
||||
bra _loop
|
||||
+ lda monochrome_dont_stipple_flag
|
||||
beq _stipple
|
||||
ldy #255 ; don't stipple
|
||||
bra _loop
|
||||
_stipple lda yy
|
||||
and #1 ; determine stipple pattern to use
|
||||
bne +
|
||||
ldy #%01010101
|
||||
bra _loop
|
||||
+ ldy #%10101010
|
||||
_loop lda length
|
||||
ora length+1
|
||||
beq _done
|
||||
sty cx16.VERA_DATA0
|
||||
lda length
|
||||
bne +
|
||||
dec length+1
|
||||
+ dec length
|
||||
bra _loop
|
||||
_done
|
||||
}}
|
||||
|
||||
if monochrome_dont_stipple_flag {
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
|
||||
if color
|
||||
cx16.VERA_DATA0 |= masked_ends[separate_pixels]
|
||||
else
|
||||
cx16.VERA_DATA0 &= ~masked_ends[separate_pixels]
|
||||
} else {
|
||||
repeat separate_pixels {
|
||||
plot(xx, yy, color)
|
||||
xx++
|
||||
}
|
||||
}
|
||||
}
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
|
||||
}
|
||||
4 -> {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
position(xx, yy)
|
||||
%asm {{
|
||||
@@ -257,8 +158,8 @@ _done
|
||||
+
|
||||
}}
|
||||
}
|
||||
6 -> {
|
||||
; highres 4c ....also mostly usable for mode 2, lores 4c?
|
||||
2 -> {
|
||||
; highres 4c ....also mostly usable for lores 4c?
|
||||
color &= 3
|
||||
ubyte[4] colorbits
|
||||
ubyte ii
|
||||
@@ -305,62 +206,27 @@ _done
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_horizontal_line(uword xx, uword yy, uword length, ubyte color) {
|
||||
; does bounds checking and clipping
|
||||
if msb(yy)&$80!=0 or yy>=height
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
length += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=width
|
||||
return
|
||||
if xx+length>width
|
||||
length = width-xx
|
||||
if length>width
|
||||
return
|
||||
|
||||
horizontal_line(xx, yy, length, color)
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
|
||||
when active_mode {
|
||||
1, 5 -> {
|
||||
; monochrome, lo-res
|
||||
cx16.r15L = gfx2.plot.maskbits[xx as ubyte & 7] ; bitmask
|
||||
if color {
|
||||
if monochrome_dont_stipple_flag {
|
||||
; draw continuous line.
|
||||
position2(xx,yy,true)
|
||||
if active_mode==1
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
; draw stippled line.
|
||||
if xx&1 {
|
||||
yy++
|
||||
lheight--
|
||||
}
|
||||
position2(xx,yy,true)
|
||||
if active_mode==1
|
||||
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
|
||||
repeat lheight/2 {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
position2(xx,yy,true)
|
||||
cx16.r15 = ~cx16.r15 ; erase pixels
|
||||
if active_mode==1
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
4 -> {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
; set vera auto-increment to 320 pixel increment (=next line)
|
||||
position(xx,yy)
|
||||
@@ -375,7 +241,7 @@ _done
|
||||
+
|
||||
}}
|
||||
}
|
||||
6 -> {
|
||||
2 -> {
|
||||
; highres 4c
|
||||
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||
if lheight==0
|
||||
@@ -505,6 +371,7 @@ _done
|
||||
}
|
||||
|
||||
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
@@ -518,36 +385,91 @@ _done
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 += (yy as word -xx)*2+1
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, color=color.
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
|
||||
; This version does bounds checks and clipping, but is a lot slower.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, color=color.
|
||||
safe_plot(cx16.r14, cx16.r15, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
@@ -560,17 +482,39 @@ _done
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
; This version does bounds checks and clipping, but is a lot slower.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, ubyte @zp color) {
|
||||
ubyte[8] @shared maskbits = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
ubyte[4] @shared mask4c = [%00111111, %11001111, %11110011, %11111100]
|
||||
ubyte[4] @shared shift4c = [6,4,2,0]
|
||||
ubyte[4] shiftedleft_4c_1 = [1<<6, 1<<4, 1<<2, 1<<0]
|
||||
@@ -579,31 +523,6 @@ _done
|
||||
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores monochrome
|
||||
if color {
|
||||
; solid color or perhaps stipple
|
||||
%asm {{
|
||||
lda xx
|
||||
eor yy
|
||||
ora monochrome_dont_stipple_flag
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
mode_1_prepare()
|
||||
%asm {{
|
||||
tsb cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
; only erase
|
||||
mode_1_prepare()
|
||||
%asm {{
|
||||
trb cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
}
|
||||
; TODO modes 2, 3
|
||||
4 -> {
|
||||
; lores 256c
|
||||
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
@@ -619,32 +538,8 @@ _done
|
||||
sta cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
5 -> {
|
||||
; highres monochrome
|
||||
if color {
|
||||
; solid color or perhaps stipple
|
||||
%asm {{
|
||||
lda xx
|
||||
eor yy
|
||||
ora monochrome_dont_stipple_flag
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
mode_5_prepare()
|
||||
%asm {{
|
||||
tsb cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
; only erase
|
||||
mode_5_prepare()
|
||||
%asm {{
|
||||
trb cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
}
|
||||
6 -> {
|
||||
; highres 4c ....also mostly usable for mode 2, lores 4c?
|
||||
2 -> {
|
||||
; highres 4c ....also mostly usable for lores 4c?
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r2L = lsb(xx) & 3 ; xbits
|
||||
; color &= 3
|
||||
@@ -670,76 +565,20 @@ _done
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub mode_1_prepare() {
|
||||
%asm {{
|
||||
lda xx
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
xx /= 8
|
||||
xx += yy*(320/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda xx+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda xx
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda maskbits,y
|
||||
}}
|
||||
}
|
||||
|
||||
sub mode_5_prepare() {
|
||||
%asm {{
|
||||
lda xx
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
xx /= 8
|
||||
xx += yy*(640/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda xx+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda xx
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda maskbits,y
|
||||
}}
|
||||
}
|
||||
sub safe_plot(uword xx, uword yy, ubyte color) {
|
||||
; A plot that does bounds checks to see if the pixel is inside the screen.
|
||||
if msb(xx)&$80!=0 or msb(yy)&$80!=0
|
||||
return
|
||||
if xx >= width or yy >= height
|
||||
return
|
||||
plot(xx, yy, color)
|
||||
}
|
||||
|
||||
sub pget(uword @zp xx, uword yy) -> ubyte {
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores monochrome
|
||||
%asm {{
|
||||
lda xx
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
xx /= 8
|
||||
xx += yy*(320/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda xx+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda xx
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda plot.maskbits,y
|
||||
and cx16.VERA_DATA0
|
||||
beq +
|
||||
lda #1
|
||||
+
|
||||
}}
|
||||
}
|
||||
; TODO modes 2, 3
|
||||
4 -> {
|
||||
; lores 256c
|
||||
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
@@ -753,31 +592,7 @@ _done
|
||||
lda cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
5 -> {
|
||||
; hires monochrome
|
||||
%asm {{
|
||||
lda xx
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
xx /= 8
|
||||
xx += yy*(640/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda xx+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda xx
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda plot.maskbits,y
|
||||
and cx16.VERA_DATA0
|
||||
beq +
|
||||
lda #1
|
||||
+
|
||||
}}
|
||||
}
|
||||
6 -> {
|
||||
2 -> {
|
||||
; hires 4c
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
%asm {{
|
||||
@@ -789,21 +604,32 @@ _done
|
||||
lda cx16.r0L
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA0
|
||||
sta cx16.r0L
|
||||
pha
|
||||
lda xx
|
||||
and #3
|
||||
tay
|
||||
lda gfx2.plot.shift4c,y
|
||||
tay
|
||||
pla
|
||||
cpy #0
|
||||
beq +
|
||||
- lsr a
|
||||
dey
|
||||
bne -
|
||||
+ and #3
|
||||
}}
|
||||
cx16.r1L = lsb(xx) & 3
|
||||
cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L]
|
||||
return cx16.r0L & 3
|
||||
}
|
||||
else -> return 0
|
||||
}
|
||||
}
|
||||
|
||||
sub fill(word @zp xx, word @zp yy, ubyte new_color) {
|
||||
sub fill(uword x, uword y, ubyte new_color) {
|
||||
; Non-recursive scanline flood fill.
|
||||
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
|
||||
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
|
||||
const ubyte MAXDEPTH = 48
|
||||
const ubyte MAXDEPTH = 64
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
word[MAXDEPTH] @split @shared stack_xr
|
||||
word[MAXDEPTH] @split @shared stack_y
|
||||
@@ -882,11 +708,15 @@ _done
|
||||
while cx16.r12L {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
while xx >= 0 and pget(xx as uword, yy as uword) == cx16.r11L {
|
||||
plot(xx as uword, yy as uword, cx16.r10L)
|
||||
; TODO: if mode==1 (256c) use vera autodecrement instead of pget(), but code bloat not worth it?
|
||||
while xx >= 0 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx--
|
||||
}
|
||||
if xx >= x1
|
||||
if x1!=xx
|
||||
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
|
||||
else
|
||||
goto skip
|
||||
|
||||
left = xx + 1
|
||||
@@ -895,17 +725,26 @@ _done
|
||||
xx = x1 + 1
|
||||
|
||||
do {
|
||||
while xx <= width-1 and pget(xx as uword, yy as uword) == cx16.r11L {
|
||||
plot(xx as uword, yy as uword, cx16.r10L)
|
||||
cx16.r9 = xx
|
||||
; TODO: if mode==1 (256c) use vera autoincrement instead of pget(), but code bloat not worth it?
|
||||
while xx <= width-1 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
if cx16.r9!=xx
|
||||
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
|
||||
|
||||
push_stack(left, xx - 1, yy, dy)
|
||||
if xx > x2 + 1
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
skip:
|
||||
xx++
|
||||
while xx <= x2 and pget(xx as uword, yy as uword) != cx16.r11L
|
||||
while xx <= x2 {
|
||||
if pget(xx as uword, yy as uword) == cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
left = xx
|
||||
} until xx>x2
|
||||
}
|
||||
@@ -914,29 +753,16 @@ skip:
|
||||
sub position(uword @zp xx, uword yy) {
|
||||
when active_mode {
|
||||
1 -> {
|
||||
; lores monochrome
|
||||
cx16.r0 = yy*(320/8) + xx/8
|
||||
cx16.vaddr(0, cx16.r0, 0, 1)
|
||||
}
|
||||
; TODO modes 2, 3
|
||||
4 -> {
|
||||
; lores 256c
|
||||
void addr_mul_24_for_lores_256c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r2L = cx16.r1L
|
||||
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
|
||||
}
|
||||
5 -> {
|
||||
; highres monochrome
|
||||
cx16.r0 = yy*(640/8) + xx/8
|
||||
cx16.vaddr(0, cx16.r0, 0, 1)
|
||||
}
|
||||
6 -> {
|
||||
2 -> {
|
||||
; highres 4c
|
||||
void addr_mul_24_for_highres_4c(yy, xx) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r2L = cx16.r1L
|
||||
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
|
||||
}
|
||||
}
|
||||
cx16.r2L = cx16.r1L
|
||||
cx16.vaddr(cx16.r2L, cx16.r0, 0, 1)
|
||||
}
|
||||
|
||||
sub position2(uword @zp xx, uword yy, bool also_port_1) {
|
||||
@@ -948,7 +774,6 @@ skip:
|
||||
inline asmsub next_pixel(ubyte color @A) {
|
||||
; -- sets the next pixel byte to the graphics chip.
|
||||
; for 8 bpp screens this will plot 1 pixel.
|
||||
; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern).
|
||||
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
|
||||
%asm {{
|
||||
sta cx16.VERA_DATA0
|
||||
@@ -958,7 +783,6 @@ skip:
|
||||
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, X, Y) {
|
||||
; -- sets the next bunch of pixels from a prepared array of bytes.
|
||||
; for 8 bpp screens this will plot 1 pixel per byte.
|
||||
; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte).
|
||||
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
@@ -1019,92 +843,7 @@ skip:
|
||||
ubyte[8] @shared char_bitmap_bytes_right
|
||||
|
||||
when active_mode {
|
||||
1, 5 -> {
|
||||
; monochrome mode, either resolution
|
||||
cx16.r3 = sctextptr
|
||||
while @(cx16.r3) {
|
||||
chardataptr = charset_addr + @(cx16.r3) * $0008
|
||||
; copy the character bitmap into RAM
|
||||
cx16.vaddr_autoincr(charset_bank, chardataptr, 0, 1)
|
||||
%asm {{
|
||||
; pre-shift the bits
|
||||
lda text.xx
|
||||
and #7
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #0
|
||||
- lda cx16.VERA_DATA0
|
||||
stz P8ZP_SCRATCH_REG
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
cpx #0
|
||||
beq +
|
||||
- lsr a
|
||||
ror P8ZP_SCRATCH_REG
|
||||
dex
|
||||
bne -
|
||||
+ sta char_bitmap_bytes_left,y
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta char_bitmap_bytes_right,y
|
||||
iny
|
||||
cpy #8
|
||||
bne --
|
||||
}}
|
||||
; left part of shifted char
|
||||
position2(xx, yy, true)
|
||||
set_autoincrs_mode1_or_5()
|
||||
if color {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_left,y
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
} else {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_left,y
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
}
|
||||
; right part of shifted char
|
||||
if lsb(xx) & 7 {
|
||||
position2(xx+8, yy, true)
|
||||
set_autoincrs_mode1_or_5()
|
||||
if color {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_right,y
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
} else {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_right,y
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
}
|
||||
}
|
||||
cx16.r3++
|
||||
xx += 8
|
||||
}
|
||||
}
|
||||
4 -> {
|
||||
1 -> {
|
||||
; lores 256c
|
||||
while @(sctextptr) {
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
@@ -1131,7 +870,7 @@ skip:
|
||||
sctextptr++
|
||||
}
|
||||
}
|
||||
6 -> {
|
||||
2 -> {
|
||||
; hires 4c
|
||||
; we're going to use a few cx16 registers to make sure every variable is in zeropage in the inner loop.
|
||||
cx16.r11L = color
|
||||
@@ -1194,34 +933,19 @@ skip:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub set_autoincrs_mode1_or_5() {
|
||||
; set autoincrements to go to next pixel row (40 or 80 increment)
|
||||
if active_mode==1 {
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
|
||||
} else {
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asmsub cs_innerloop640() clobbers(Y) {
|
||||
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
|
||||
%asm {{
|
||||
ldy #80
|
||||
- stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
stz cx16.VERA_DATA0
|
||||
- sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
|
||||
@@ -5,13 +5,19 @@
|
||||
; Wraps the graphics functions that are in ROM.
|
||||
; Only lo-res 320x240 256 color mode for now.
|
||||
; Unlike graphics module on the C64, you can use colors() to set new drawing colors for every draw operation.
|
||||
; For other resolutions or other color modes, use the "gfx2" module instead. (which is Cx16-specific)
|
||||
; For other resolutions or other color modes, use the "gfx2" or "monogfx" module instead. (which is Cx16-specific)
|
||||
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
;
|
||||
|
||||
|
||||
graphics {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the normal FB_cursor_position() call but reuses existing r0 and r1
|
||||
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 240
|
||||
|
||||
@@ -69,6 +75,8 @@ graphics {
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
|
||||
;cx16.r0 = xcenter - radius/2
|
||||
;cx16.r1 = ycenter - radius/2
|
||||
;cx16.r2 = radius*2
|
||||
@@ -85,43 +93,47 @@ graphics {
|
||||
while xx>=yy {
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter + yy
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter - yy
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter + xx
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter - xx
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0 {
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
} else {
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 += (yy as word -xx)*2+1
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
@@ -132,13 +144,14 @@ graphics {
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,911 @@
|
||||
; Monochrome Bitmap pixel graphics routines for the CommanderX16
|
||||
; Using the full-screen 640x480 and 320x240 screen modes, in 1 bpp mode (black/white).
|
||||
;
|
||||
; No text layer is currently shown, but text can be drawn as part of the bitmap itself.
|
||||
; For color bitmap graphics, see the gfx2 library.
|
||||
;
|
||||
; NOTE: a lot of the code here is similar or the same to that in gfx2
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
;
|
||||
|
||||
monogfx {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; read-only control variables:
|
||||
uword width = 0
|
||||
uword height = 0
|
||||
bool dont_stipple_flag = true ; set to false to enable stippling mode
|
||||
|
||||
sub lores() {
|
||||
; enable 320*240 bitmap mode
|
||||
cx16.VERA_CTRL=0
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 64
|
||||
cx16.VERA_DC_VSCALE = 64
|
||||
cx16.VERA_L1_CONFIG = %00000100
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
width = 320
|
||||
height = 240
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub hires() {
|
||||
; enable 640*480 bitmap mode
|
||||
cx16.VERA_CTRL=0
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 128
|
||||
cx16.VERA_DC_VSCALE = 128
|
||||
cx16.VERA_L1_CONFIG = %00000100
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = %00000001
|
||||
width = 640
|
||||
height = 480
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub textmode() {
|
||||
; back to normal text mode
|
||||
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||
cbm.CINT()
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte color) {
|
||||
stipple(false)
|
||||
position(0, 0)
|
||||
when width {
|
||||
320 -> {
|
||||
repeat 240/2/8
|
||||
cs_innerloop640(color)
|
||||
}
|
||||
640 -> {
|
||||
repeat 480/8
|
||||
cs_innerloop640(color)
|
||||
}
|
||||
}
|
||||
position(0, 0)
|
||||
}
|
||||
|
||||
sub stipple(bool enable) {
|
||||
dont_stipple_flag = not enable
|
||||
}
|
||||
|
||||
sub rect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
|
||||
if rwidth==0 or rheight==0
|
||||
return
|
||||
horizontal_line(xx, yy, rwidth, draw)
|
||||
if rheight==1
|
||||
return
|
||||
horizontal_line(xx, yy+rheight-1, rwidth, draw)
|
||||
vertical_line(xx, yy+1, rheight-2, draw)
|
||||
if rwidth==1
|
||||
return
|
||||
vertical_line(xx+rwidth-1, yy+1, rheight-2, draw)
|
||||
}
|
||||
|
||||
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
|
||||
; Draw a filled rectangle of the given size and color.
|
||||
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
||||
if rwidth==0
|
||||
return
|
||||
repeat rheight {
|
||||
horizontal_line(xx, yy, rwidth, draw)
|
||||
yy++
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword xx, uword yy, uword length, bool draw) {
|
||||
ubyte[9] masked_starts = [ 0, %00000001, %00000011, %00000111, %00001111, %00011111, %00111111, %01111111, %11111111]
|
||||
ubyte[9] masked_ends = [ 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110, %11111111]
|
||||
|
||||
if length==0
|
||||
return
|
||||
if length<=8 {
|
||||
; just use 2 byte writes with shifted mask
|
||||
position2(xx,yy,true)
|
||||
%asm {{
|
||||
ldy length
|
||||
lda masked_ends,y
|
||||
sta cx16.r0L ; save left byte
|
||||
stz P8ZP_SCRATCH_B1
|
||||
lda xx
|
||||
and #7
|
||||
beq +
|
||||
tay
|
||||
lda cx16.r0L
|
||||
- lsr a
|
||||
ror P8ZP_SCRATCH_B1
|
||||
dey
|
||||
bne -
|
||||
sta cx16.r0L ; new left byte
|
||||
+
|
||||
lda dont_stipple_flag
|
||||
bne _dontstipple
|
||||
; determine stipple pattern
|
||||
lda yy
|
||||
and #1
|
||||
beq +
|
||||
lda #%10101010
|
||||
bne ++
|
||||
+ lda #%01010101
|
||||
+ sta P8ZP_SCRATCH_REG
|
||||
lda cx16.r0L
|
||||
and P8ZP_SCRATCH_REG
|
||||
sta cx16.r0L
|
||||
lda P8ZP_SCRATCH_B1
|
||||
and P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_B1
|
||||
_dontstipple
|
||||
lda draw
|
||||
beq _clear
|
||||
lda cx16.r0L ; left byte
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
lda P8ZP_SCRATCH_B1 ; right byte
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
_clear
|
||||
lda cx16.r0L ; left byte
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
lda P8ZP_SCRATCH_B1 ; right byte
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
ubyte separate_pixels = (8-lsb(xx)) & 7
|
||||
if separate_pixels {
|
||||
if dont_stipple_flag {
|
||||
position(xx,yy)
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
|
||||
if draw
|
||||
cx16.VERA_DATA0 |= masked_starts[separate_pixels]
|
||||
else
|
||||
cx16.VERA_DATA0 &= ~masked_starts[separate_pixels]
|
||||
xx += separate_pixels
|
||||
} else {
|
||||
repeat separate_pixels {
|
||||
plot(xx, yy, draw)
|
||||
xx++
|
||||
}
|
||||
}
|
||||
length -= separate_pixels
|
||||
}
|
||||
if length {
|
||||
position(xx, yy)
|
||||
separate_pixels = lsb(length) & 7
|
||||
xx += length & $fff8
|
||||
%asm {{
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lsr length+1
|
||||
ror length
|
||||
lda draw
|
||||
bne +
|
||||
ldy #0 ; black
|
||||
bra _loop
|
||||
+ lda dont_stipple_flag
|
||||
beq _stipple
|
||||
ldy #255 ; don't stipple
|
||||
bra _loop
|
||||
_stipple lda yy
|
||||
and #1 ; determine stipple pattern to use
|
||||
bne +
|
||||
ldy #%01010101
|
||||
bra _loop
|
||||
+ ldy #%10101010
|
||||
_loop lda length
|
||||
ora length+1
|
||||
beq _done
|
||||
sty cx16.VERA_DATA0
|
||||
lda length
|
||||
bne +
|
||||
dec length+1
|
||||
+ dec length
|
||||
bra _loop
|
||||
_done
|
||||
}}
|
||||
|
||||
if dont_stipple_flag {
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off
|
||||
if draw
|
||||
cx16.VERA_DATA0 |= masked_ends[separate_pixels]
|
||||
else
|
||||
cx16.VERA_DATA0 &= ~masked_ends[separate_pixels]
|
||||
} else {
|
||||
repeat separate_pixels {
|
||||
plot(xx, yy, draw)
|
||||
xx++
|
||||
}
|
||||
}
|
||||
}
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
|
||||
}
|
||||
|
||||
sub safe_horizontal_line(uword xx, uword yy, uword length, bool draw) {
|
||||
; does bounds checking and clipping
|
||||
if msb(yy)&$80!=0 or yy>=height
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
length += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=width
|
||||
return
|
||||
if xx+length>width
|
||||
length = width-xx
|
||||
if length>width
|
||||
return
|
||||
|
||||
horizontal_line(xx, yy, length, draw)
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
|
||||
cx16.r15L = monogfx.plot.maskbits[xx as ubyte & 7] ; bitmask
|
||||
if draw {
|
||||
if dont_stipple_flag {
|
||||
; draw continuous line.
|
||||
position2(xx,yy,true)
|
||||
if width==320
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
; draw stippled line.
|
||||
if (xx ^ yy)&1==0 {
|
||||
yy++
|
||||
lheight--
|
||||
}
|
||||
lheight++ ; because it is divided by 2 later, don't round off the last pixel
|
||||
position2(xx,yy,true)
|
||||
if width==320
|
||||
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
|
||||
repeat lheight/2 {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
ora cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
position2(xx,yy,true)
|
||||
cx16.r15 = ~cx16.r15 ; erase pixels
|
||||
if width==320
|
||||
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||
else
|
||||
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||
repeat lheight {
|
||||
%asm {{
|
||||
lda cx16.VERA_DATA0
|
||||
and cx16.r15L
|
||||
sta cx16.VERA_DATA1
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub set_both_strides(ubyte stride) {
|
||||
stride <<= 4
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, bool draw) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
cx16.r0 = x1
|
||||
x1 = x2
|
||||
x2 = cx16.r0
|
||||
cx16.r0 = y1
|
||||
y1 = y2
|
||||
y2 = cx16.r0
|
||||
}
|
||||
word @zp dx = (x2 as word)-x1
|
||||
word @zp dy = (y2 as word)-y1
|
||||
|
||||
if dx==0 {
|
||||
vertical_line(x1, y1, abs(dy) as uword +1, draw)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx) as uword +1, draw)
|
||||
return
|
||||
}
|
||||
|
||||
word @zp d = 0
|
||||
cx16.r1L = true ; 'positive_ix'
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
cx16.r1L = false
|
||||
}
|
||||
word @zp dx2 = dx*2
|
||||
word @zp dy2 = dy*2
|
||||
cx16.r14 = x1 ; internal plot X
|
||||
|
||||
if dx >= dy {
|
||||
if cx16.r1L {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14++
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14--
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if cx16.r1L {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14++
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14--
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, draw=draw
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
|
||||
; Does bounds checking and clipping.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, draw=draw
|
||||
safe_plot(cx16.r14, cx16.r15, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Does bounds checking and clipping.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
|
||||
ubyte[8] @shared maskbits = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
if draw {
|
||||
; solid color or perhaps stipple
|
||||
%asm {{
|
||||
lda xx
|
||||
eor yy
|
||||
ora dont_stipple_flag
|
||||
and #1
|
||||
}}
|
||||
if_nz {
|
||||
prepare()
|
||||
%asm {{
|
||||
tsb cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
} else {
|
||||
; only erase
|
||||
prepare()
|
||||
%asm {{
|
||||
trb cx16.VERA_DATA0
|
||||
}}
|
||||
}
|
||||
|
||||
sub prepare() {
|
||||
%asm {{
|
||||
lda xx
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
xx /= 8
|
||||
if width==320
|
||||
xx += yy*(320/8)
|
||||
else
|
||||
xx += yy*(640/8)
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda xx+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda xx
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda maskbits,y
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_plot(uword xx, uword yy, bool draw) {
|
||||
; A plot that does bounds checks to see if the pixel is inside the screen.
|
||||
if msb(xx)&$80!=0 or msb(yy)&$80!=0
|
||||
return
|
||||
if xx >= width or yy >= height
|
||||
return
|
||||
plot(xx, yy, draw)
|
||||
}
|
||||
|
||||
sub pget(uword @zp xx, uword yy) -> ubyte {
|
||||
%asm {{
|
||||
lda xx
|
||||
and #7
|
||||
pha ; xbits
|
||||
}}
|
||||
xx /= 8
|
||||
if width==320
|
||||
xx += yy*(320/8)
|
||||
else
|
||||
xx += yy*(640/8)
|
||||
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
lda xx+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda xx
|
||||
sta cx16.VERA_ADDR_L
|
||||
ply ; xbits
|
||||
lda plot.maskbits,y
|
||||
and cx16.VERA_DATA0
|
||||
beq +
|
||||
lda #1
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub fill(uword x, uword y, bool draw) {
|
||||
; Non-recursive scanline flood fill.
|
||||
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
|
||||
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
|
||||
const ubyte MAXDEPTH = 64
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
word[MAXDEPTH] @split @shared stack_xr
|
||||
word[MAXDEPTH] @split @shared stack_y
|
||||
byte[MAXDEPTH] @shared stack_dy
|
||||
cx16.r12L = 0 ; stack pointer
|
||||
word x1
|
||||
word x2
|
||||
byte dy
|
||||
cx16.r10L = draw as ubyte
|
||||
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
|
||||
if cx16.r12L==MAXDEPTH
|
||||
return
|
||||
cx16.r0s = sy+sdy
|
||||
if cx16.r0s>=0 and cx16.r0s<=height-1 {
|
||||
;; stack_xl[cx16.r12L] = sxl
|
||||
;; stack_xr[cx16.r12L] = sxr
|
||||
;; stack_y[cx16.r12L] = sy
|
||||
;; stack_dy[cx16.r12L] = sdy
|
||||
;; cx16.r12L++
|
||||
%asm {{
|
||||
ldy cx16.r12L
|
||||
lda sxl
|
||||
sta stack_xl_lsb,y
|
||||
lda sxl+1
|
||||
sta stack_xl_msb,y
|
||||
lda sxr
|
||||
sta stack_xr_lsb,y
|
||||
lda sxr+1
|
||||
sta stack_xr_msb,y
|
||||
lda sy
|
||||
sta stack_y_lsb,y
|
||||
lda sy+1
|
||||
sta stack_y_msb,y
|
||||
ldy cx16.r12L
|
||||
lda sdy
|
||||
sta stack_dy,y
|
||||
inc cx16.r12L
|
||||
}}
|
||||
}
|
||||
}
|
||||
sub pop_stack() {
|
||||
;; cx16.r12L--
|
||||
;; x1 = stack_xl[cx16.r12L]
|
||||
;; x2 = stack_xr[cx16.r12L]
|
||||
;; y = stack_y[cx16.r12L]
|
||||
;; dy = stack_dy[cx16.r12L]
|
||||
%asm {{
|
||||
dec cx16.r12L
|
||||
ldy cx16.r12L
|
||||
lda stack_xl_lsb,y
|
||||
sta x1
|
||||
lda stack_xl_msb,y
|
||||
sta x1+1
|
||||
lda stack_xr_lsb,y
|
||||
sta x2
|
||||
lda stack_xr_msb,y
|
||||
sta x2+1
|
||||
lda stack_y_lsb,y
|
||||
sta yy
|
||||
lda stack_y_msb,y
|
||||
sta yy+1
|
||||
ldy cx16.r12L
|
||||
lda stack_dy,y
|
||||
sta dy
|
||||
}}
|
||||
yy+=dy
|
||||
}
|
||||
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
|
||||
if cx16.r11L == cx16.r10L
|
||||
return
|
||||
if xx<0 or xx>width-1 or yy<0 or yy>height-1
|
||||
return
|
||||
push_stack(xx, xx, yy, 1)
|
||||
push_stack(xx, xx, yy + 1, -1)
|
||||
word left = 0
|
||||
while cx16.r12L {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
while xx >= 0 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx--
|
||||
}
|
||||
if x1!=xx
|
||||
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
|
||||
else
|
||||
goto skip
|
||||
|
||||
left = xx + 1
|
||||
if left < x1
|
||||
push_stack(left, x1 - 1, yy, -dy)
|
||||
xx = x1 + 1
|
||||
|
||||
do {
|
||||
cx16.r9 = xx
|
||||
while xx <= width-1 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
if cx16.r9!=xx
|
||||
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
|
||||
|
||||
push_stack(left, xx - 1, yy, dy)
|
||||
if xx > x2 + 1
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
skip:
|
||||
xx++
|
||||
while xx <= x2 {
|
||||
if pget(xx as uword, yy as uword) == cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
left = xx
|
||||
} until xx>x2
|
||||
}
|
||||
}
|
||||
|
||||
sub position(uword @zp xx, uword yy) {
|
||||
if width==320
|
||||
cx16.r0 = yy*(320/8)
|
||||
else
|
||||
cx16.r0 = yy*(640/8)
|
||||
cx16.vaddr(0, cx16.r0+(xx/8), 0, 1)
|
||||
}
|
||||
|
||||
sub position2(uword @zp xx, uword yy, bool also_port_1) {
|
||||
position(xx, yy)
|
||||
if also_port_1
|
||||
cx16.vaddr_clone(0)
|
||||
}
|
||||
|
||||
const ubyte charset_bank = $1
|
||||
const uword charset_addr = $f000 ; in bank 1, so $1f000
|
||||
|
||||
sub text_charset(ubyte charset) {
|
||||
; -- select the text charset to use with the text() routine
|
||||
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
|
||||
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
|
||||
cx16.screen_set_charset(charset, 0)
|
||||
}
|
||||
|
||||
sub text(uword @zp xx, uword yy, bool draw, uword sctextptr) {
|
||||
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
||||
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||
uword chardataptr
|
||||
ubyte[8] @shared char_bitmap_bytes_left
|
||||
ubyte[8] @shared char_bitmap_bytes_right
|
||||
|
||||
cx16.r3 = sctextptr
|
||||
while @(cx16.r3) {
|
||||
chardataptr = charset_addr + @(cx16.r3) * $0008
|
||||
; copy the character bitmap into RAM
|
||||
cx16.vaddr_autoincr(charset_bank, chardataptr, 0, 1)
|
||||
%asm {{
|
||||
; pre-shift the bits
|
||||
lda text.xx
|
||||
and #7
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #0
|
||||
- lda cx16.VERA_DATA0
|
||||
stz P8ZP_SCRATCH_REG
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
cpx #0
|
||||
beq +
|
||||
- lsr a
|
||||
ror P8ZP_SCRATCH_REG
|
||||
dex
|
||||
bne -
|
||||
+ sta char_bitmap_bytes_left,y
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta char_bitmap_bytes_right,y
|
||||
iny
|
||||
cpy #8
|
||||
bne --
|
||||
}}
|
||||
; left part of shifted char
|
||||
position2(xx, yy, true)
|
||||
set_autoincrs()
|
||||
if draw {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_left,y
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
} else {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_left,y
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
}
|
||||
; right part of shifted char
|
||||
if lsb(xx) & 7 {
|
||||
position2(xx+8, yy, true)
|
||||
set_autoincrs()
|
||||
if draw {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_right,y
|
||||
ora cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
} else {
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda char_bitmap_bytes_right,y
|
||||
eor #255
|
||||
and cx16.VERA_DATA1
|
||||
sta cx16.VERA_DATA0
|
||||
iny
|
||||
cpy #8
|
||||
bne -
|
||||
}}
|
||||
}
|
||||
}
|
||||
cx16.r3++
|
||||
xx += 8
|
||||
}
|
||||
|
||||
sub set_autoincrs() {
|
||||
; set autoincrements to go to next pixel row (40 or 80 increment)
|
||||
if width==320 {
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
|
||||
} else {
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
|
||||
%asm {{
|
||||
cmp #0
|
||||
beq +
|
||||
lda #255
|
||||
+ ldy #80
|
||||
- sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -79,15 +79,13 @@ palette {
|
||||
|
||||
sub set_grayscale() {
|
||||
vera_palette_ptr = $fa00
|
||||
cc=0
|
||||
repeat 16 {
|
||||
cc=0
|
||||
repeat 16 {
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
cc += $11
|
||||
}
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
cc += $11
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,40 +148,34 @@ palette {
|
||||
|
||||
sub set_c64pepto() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
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++
|
||||
}
|
||||
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++
|
||||
}
|
||||
}
|
||||
|
||||
sub set_c64light() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
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++
|
||||
}
|
||||
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_c64dark() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
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++
|
||||
}
|
||||
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++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,6 @@ sprites {
|
||||
}
|
||||
|
||||
sub set_palette_offset(ubyte spritenum, ubyte offset) {
|
||||
sprite_reg = VERA_SPRITEREGS + 7 + spritenum*$0008
|
||||
cx16.vpoke_mask(1, sprite_reg, %11110000, offset>>4)
|
||||
cx16.vpoke_mask(1, VERA_SPRITEREGS + 7 + spritenum*$0008, %11110000, offset>>4)
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ cbm {
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.home or txt.plot
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip, including resetting to the default color palette
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, IRQ, ...)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
@@ -40,7 +40,7 @@ romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device. See also BSAVE
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
@@ -120,6 +120,9 @@ 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
|
||||
|
||||
|
||||
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
|
||||
&uword r0 = $0002
|
||||
@@ -267,6 +270,17 @@ cx16 {
|
||||
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
|
||||
&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!!!)
|
||||
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_MULT = 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)
|
||||
|
||||
|
||||
; VERA_PSG_BASE = $1F9C0
|
||||
; VERA_PALETTE_BASE = $1FA00
|
||||
; VERA_SPRITES_BASE = $1FC00
|
||||
@@ -320,17 +334,16 @@ cx16 {
|
||||
; spelling of the names is taken from the Commander X-16 rom sources
|
||||
|
||||
; supported C128 additions
|
||||
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
|
||||
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
|
||||
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff4a = CLOSE_ALL(ubyte device @A) clobbers(A,X,Y)
|
||||
romsub $ff59 = LKUPLA(ubyte la @A) clobbers(A,X,Y)
|
||||
romsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) clobbers(X, Y) -> ubyte @A, bool @Pc ; note: X,Y size result is not supported, use SCREEN or get_screen_mode routine for that
|
||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||
romsub $ff6e = jsrfar() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
romsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7d = primm()
|
||||
romsub $ff7d = PRIMM()
|
||||
|
||||
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
|
||||
|
||||
@@ -354,7 +367,6 @@ romsub $fef6 = FB_init() clobbers(A,X,Y)
|
||||
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
|
||||
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte colorcount @X) clobbers(A,X,Y)
|
||||
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
|
||||
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the previous routine, but avoiding having to respecify both x and y every time
|
||||
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
|
||||
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
|
||||
romsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||
@@ -384,7 +396,8 @@ romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
||||
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
|
||||
romsub $ff44 = macptr(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $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()
|
||||
@@ -396,7 +409,7 @@ romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to
|
||||
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
|
||||
romsub $fec3 = kbdbuf_put(ubyte key @A) clobbers(X)
|
||||
romsub $fed2 = keymap(uword identifier @XY, bool read @Pc) -> bool @Pc
|
||||
romsub $ff68 = mouse_config(ubyte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
|
||||
romsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
|
||||
romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
|
||||
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
@@ -439,7 +452,7 @@ asmsub kbdbuf_clear() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
|
||||
asmsub mouse_config2(byte shape @A) clobbers (A, X, Y) {
|
||||
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
|
||||
%asm {{
|
||||
pha ; save shape
|
||||
@@ -515,16 +528,12 @@ asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
|
||||
; -- get a byte from VERA's video memory
|
||||
; note: inefficient when reading multiple sequential bytes!
|
||||
%asm {{
|
||||
pha
|
||||
lda cx16.VERA_CTRL
|
||||
ora #1
|
||||
sta cx16.VERA_CTRL
|
||||
pla
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
sty cx16.VERA_ADDR_M
|
||||
stx cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA1
|
||||
lda cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -821,6 +830,24 @@ 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.
|
||||
; 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
|
||||
lda $00
|
||||
pha
|
||||
stz $00
|
||||
stx cx16.edkeyvec
|
||||
sty cx16.edkeyvec+1
|
||||
pla
|
||||
sta $00
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sys {
|
||||
@@ -862,6 +889,8 @@ asmsub init_system() {
|
||||
jsr cbm.CHROUT
|
||||
lda #147 ; clear screen
|
||||
jsr cbm.CHROUT
|
||||
lda #8 ; disable charset case switch
|
||||
jsr cbm.CHROUT
|
||||
lda #PROG8_VARSHIGH_RAMBANK
|
||||
sta $00 ; select ram bank
|
||||
lda #0
|
||||
@@ -1239,6 +1268,20 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub disable_caseswitch() {
|
||||
%asm {{
|
||||
lda #8
|
||||
jsr cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub enable_caseswitch() {
|
||||
%asm {{
|
||||
lda #9
|
||||
jsr cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
|
||||
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
; these work with PETSCII encoding instead.
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -42,6 +44,43 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tax
|
||||
clc
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_row() -> ubyte @X {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tya
|
||||
ldy #$00
|
||||
sta (colptr),y
|
||||
txa
|
||||
sta (rowptr),y
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A, X) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
%asm {{
|
||||
@@ -384,10 +423,10 @@ _nextline
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; ---- print null terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
@@ -572,7 +611,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
@@ -756,4 +796,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
; Experimental Vera FX support.
|
||||
; Docs:
|
||||
; https://github.com/X16Community/x16-docs/blob/master/VERA%20FX%20Reference.md
|
||||
; https://docs.google.com/document/d/1q34uWOiM3Be2pnaHRVgSdHySI-qsiQWPTo_gfE54PTg/edit
|
||||
|
||||
verafx {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
sub available() -> bool {
|
||||
; returns true if Vera FX is available (Vera V0.3.1 or later), false if not.
|
||||
cx16.r1L = 0
|
||||
cx16.r0L = cx16.VERA_CTRL
|
||||
cx16.VERA_CTRL = $7e
|
||||
if cx16.VERA_DC_VER0 == $56 {
|
||||
; Vera version number is valid.
|
||||
; Vera fx is available on Vera version 0.3.1 and later,
|
||||
; so no need to even check VERA_DC_VER1, which contains 0 (or higher)
|
||||
cx16.r1L = mkword(cx16.VERA_DC_VER2, cx16.VERA_DC_VER3) >= $0301
|
||||
}
|
||||
cx16.VERA_CTRL = cx16.r0L
|
||||
return cx16.r1L
|
||||
}
|
||||
|
||||
sub clear(ubyte vbank, uword vaddr, ubyte data, uword amountof32bits) {
|
||||
; use cached 4-byte write to quickly clear a portion of the video memory to a given byte value
|
||||
; this routine is around 3 times faster as gfx2.clear_screen()
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = vbank | %00110000 ; 4-byte increment
|
||||
cx16.VERA_ADDR_M = msb(vaddr)
|
||||
cx16.VERA_ADDR_L = lsb(vaddr)
|
||||
cx16.VERA_CTRL = 6<<1 ; dcsel = 6, fill the 32 bits cache
|
||||
cx16.VERA_FX_CACHE_L = data
|
||||
cx16.VERA_FX_CACHE_M = data
|
||||
cx16.VERA_FX_CACHE_H = data
|
||||
cx16.VERA_FX_CACHE_U = data
|
||||
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
|
||||
cx16.VERA_FX_MULT = 0
|
||||
cx16.VERA_FX_CTRL = %01000000 ; cache write enable
|
||||
|
||||
if (amountof32bits & %1111110000000011) == 0 {
|
||||
repeat lsb(amountof32bits >> 2)
|
||||
unroll 4 cx16.VERA_DATA0=0 ; write 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
|
||||
}
|
||||
else if (lsb(amountof32bits) & 3) == 0 {
|
||||
repeat amountof32bits >> 2
|
||||
unroll 4 cx16.VERA_DATA0=0 ; write 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
|
||||
}
|
||||
else {
|
||||
repeat amountof32bits
|
||||
cx16.VERA_DATA0=0 ; write 4 bytes at a time
|
||||
}
|
||||
|
||||
cx16.VERA_FX_CTRL = 0 ; cache write disable
|
||||
cx16.VERA_CTRL = 0
|
||||
}
|
||||
|
||||
; unsigned multiplication just passes the values as signed to muls
|
||||
; if you do this yourself in your call to muls, it will save a few instructions.
|
||||
sub mult(uword value1, uword value2) -> uword {
|
||||
; Returns the lower 16 bits of the 32 bits result,
|
||||
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
|
||||
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
|
||||
return muls(value1 as word, value2 as word) as uword
|
||||
}
|
||||
|
||||
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY {
|
||||
; Returns the lower 16 bits of the 32 bits result in AY,
|
||||
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
|
||||
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
|
||||
%asm {{
|
||||
lda #(2 << 1)
|
||||
sta cx16.VERA_CTRL ; $9F25
|
||||
stz cx16.VERA_FX_CTRL ; $9F29 (mainly to reset Addr1 Mode to 0)
|
||||
lda #%00010000
|
||||
sta cx16.VERA_FX_MULT ; $9F2C
|
||||
lda #(6 << 1)
|
||||
sta cx16.VERA_CTRL ; $9F25
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta cx16.VERA_FX_CACHE_L ; $9F29
|
||||
sty cx16.VERA_FX_CACHE_M ; $9F2A
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
sta cx16.VERA_FX_CACHE_H ; $9F2B
|
||||
sty cx16.VERA_FX_CACHE_U ; $9F2C
|
||||
lda cx16.VERA_FX_ACCUM_RESET ; $9F29 (DCSEL=6)
|
||||
|
||||
; Set the ADDR0 pointer to $1f9bc and write our multiplication result there
|
||||
; (these are the 4 bytes just before the PSG registers start)
|
||||
lda #(2 << 1)
|
||||
sta cx16.VERA_CTRL
|
||||
lda #%01000000 ; Cache Write Enable
|
||||
sta cx16.VERA_FX_CTRL
|
||||
lda #$bc
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda #$f9
|
||||
sta cx16.VERA_ADDR_M
|
||||
lda #$01
|
||||
sta cx16.VERA_ADDR_H ; no increment
|
||||
stz cx16.VERA_DATA0 ; multiply and write out result
|
||||
lda #%00010001 ; $01 with Increment 1
|
||||
sta cx16.VERA_ADDR_H ; so we can read out the result
|
||||
lda cx16.VERA_DATA0
|
||||
ldy cx16.VERA_DATA0
|
||||
ldx cx16.VERA_DATA0 ; store the upper 16 bits of the result in r0
|
||||
stx cx16.r0
|
||||
ldx cx16.VERA_DATA0
|
||||
stx cx16.r0+1
|
||||
stz cx16.VERA_FX_CTRL ; Cache write disable
|
||||
stz cx16.VERA_CTRL ; reset DCSEL
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub transparency(bool enable) {
|
||||
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
|
||||
if enable
|
||||
cx16.VERA_FX_CTRL |= %10000000
|
||||
else
|
||||
cx16.VERA_FX_CTRL &= %01111111
|
||||
cx16.VERA_CTRL = 0
|
||||
}
|
||||
}
|
||||
@@ -7,22 +7,33 @@
|
||||
diskio {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
const ubyte READ_IO_CHANNEL=12
|
||||
const ubyte WRITE_IO_CHANNEL=13
|
||||
|
||||
ubyte drivenumber = 8
|
||||
|
||||
sub set_drive(ubyte number) {
|
||||
drivenumber = number
|
||||
}
|
||||
|
||||
sub reset_read_channel() {
|
||||
void cbm.CHKIN(READ_IO_CHANNEL)
|
||||
}
|
||||
|
||||
sub reset_write_channel() {
|
||||
cbm.CHKOUT(WRITE_IO_CHANNEL)
|
||||
}
|
||||
|
||||
sub directory() -> bool {
|
||||
; -- Prints the directory contents to the screen. Returns success.
|
||||
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(12, drivenumber, 0)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(12) ; use #12 as input channel
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@@ -60,7 +71,7 @@ diskio {
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(12)
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
|
||||
if status and status & $40 == 0 { ; bit 6=end of file
|
||||
txt.print("\ni/o error, status: ")
|
||||
@@ -76,12 +87,12 @@ io_error:
|
||||
; -- Returns pointer to disk name string or 0 if failure.
|
||||
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(12, drivenumber, 0)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
ubyte okay = false
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(12) ; use #12 as input channel
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@@ -96,7 +107,7 @@ io_error:
|
||||
@(cx16.r0) = cbm.CHRIN()
|
||||
if @(cx16.r0)=='"' {
|
||||
@(cx16.r0) = ' '
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&list_filename {
|
||||
@(cx16.r0) = 0
|
||||
cx16.r0--
|
||||
}
|
||||
@@ -108,7 +119,7 @@ io_error:
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(12)
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
if okay
|
||||
return &list_filename
|
||||
return 0
|
||||
@@ -126,7 +137,7 @@ io_error:
|
||||
|
||||
sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
|
||||
; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
|
||||
; Files in the buffer are separeted by a 0 byte. You can provide an optional pattern to match against.
|
||||
; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
|
||||
; After the last filename one additional 0 byte is placed to indicate the end of the list.
|
||||
; Returns number of files (it skips 'dir' entries i.e. subdirectories).
|
||||
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
|
||||
@@ -152,7 +163,7 @@ io_error:
|
||||
return files_found
|
||||
}
|
||||
|
||||
; ----- iterative file lister functions (uses io channel 12) -----
|
||||
; ----- iterative file lister functions (uses the read io channel) -----
|
||||
|
||||
sub lf_start_list(uword pattern_ptr) -> bool {
|
||||
; -- start an iterative file listing with optional pattern matching.
|
||||
@@ -163,11 +174,11 @@ io_error:
|
||||
iteration_in_progress = true
|
||||
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(12, drivenumber, 0)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
void cbm.OPEN() ; open 12,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(12) ; use #12 as input channel
|
||||
reset_read_channel()
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
@@ -192,7 +203,7 @@ io_error:
|
||||
return false
|
||||
|
||||
repeat {
|
||||
void cbm.CHKIN(12) ; use #12 as input channel again
|
||||
reset_read_channel() ; use the input io channel again
|
||||
|
||||
uword nameptr = &list_filename
|
||||
ubyte blocks_lsb = cbm.CHRIN()
|
||||
@@ -253,13 +264,13 @@ close_end:
|
||||
; -- end an iterative file listing session (close channels).
|
||||
if iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(12)
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file loader functions (uses io channel 12) -----
|
||||
; ----- iterative file loader functions (uses the input io channel) -----
|
||||
|
||||
sub f_open(uword filenameptr) -> bool {
|
||||
; -- open a file for iterative reading with f_read
|
||||
@@ -267,18 +278,18 @@ close_end:
|
||||
f_close()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(12, drivenumber, 12) ; note: has to be 12,x,12 because otherwise f_seek doesn't work
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL) ; note: has to be Channel,x,Channel because otherwise f_seek doesn't work
|
||||
void cbm.OPEN() ; open 12,8,12,"filename"
|
||||
if_cc {
|
||||
if cbm.READST()==0 {
|
||||
iteration_in_progress = true
|
||||
void cbm.CHKIN(12) ; use #12 as input channel
|
||||
reset_read_channel()
|
||||
if_cc {
|
||||
void cbm.CHRIN() ; read first byte to test for file not found
|
||||
if not cbm.READST() {
|
||||
cbm.CLOSE(12) ; close file because we already consumed first byte
|
||||
cbm.CLOSE(READ_IO_CHANNEL) ; close file because we already consumed first byte
|
||||
void cbm.OPEN() ; re-open the file
|
||||
void cbm.CHKIN(12)
|
||||
cbm.CLRCHN() ; reset default i/o channels
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -294,6 +305,7 @@ close_end:
|
||||
if not iteration_in_progress or not num_bytes
|
||||
return 0
|
||||
|
||||
reset_read_channel()
|
||||
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||
|
||||
%asm {{
|
||||
@@ -328,6 +340,7 @@ m_in_buffer sta $ffff
|
||||
if not iteration_in_progress
|
||||
return 0
|
||||
|
||||
reset_read_channel()
|
||||
uword total_read = 0
|
||||
while not cbm.READST() {
|
||||
cx16.r0 = f_read(bufferpointer, 256)
|
||||
@@ -346,8 +359,7 @@ m_in_buffer sta $ffff
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx #12
|
||||
jsr cbm.CHKIN ; use channel 12 again for input
|
||||
jsr reset_read_channel
|
||||
ldy #0
|
||||
_loop jsr cbm.CHRIN
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
@@ -368,25 +380,23 @@ _end rts
|
||||
; -- end an iterative file loading session (close channels).
|
||||
if iteration_in_progress {
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(12)
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
iteration_in_progress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; ----- iterative file writing functions (uses io channel 13) -----
|
||||
; ----- iterative file writing functions (uses write io channel) -----
|
||||
|
||||
sub f_open_w(uword filenameptr) -> bool {
|
||||
; -- open a file for iterative writing with f_write
|
||||
f_close_w()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(13, drivenumber, 1)
|
||||
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
|
||||
void cbm.OPEN() ; open 13,8,1,"filename"
|
||||
if_cc {
|
||||
cbm.CHKOUT(13) ; use #13 as output channel
|
||||
if_cc
|
||||
return not cbm.READST()
|
||||
}
|
||||
f_close_w()
|
||||
return false
|
||||
}
|
||||
@@ -394,7 +404,7 @@ _end rts
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||
; -- write the given number of bytes to the currently open file
|
||||
if num_bytes!=0 {
|
||||
cbm.CHKOUT(13) ; use #13 as output channel again
|
||||
reset_write_channel()
|
||||
repeat num_bytes {
|
||||
cbm.CHROUT(@(bufferpointer))
|
||||
bufferpointer++
|
||||
@@ -407,7 +417,7 @@ _end rts
|
||||
sub f_close_w() {
|
||||
; -- end an iterative file writing session (close channels).
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(13)
|
||||
cbm.CLOSE(WRITE_IO_CHANNEL)
|
||||
}
|
||||
|
||||
|
||||
@@ -466,9 +476,6 @@ io_error:
|
||||
if_cc
|
||||
cx16.r0L = cbm.READST()==0
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
@@ -496,8 +503,6 @@ io_error:
|
||||
+
|
||||
}}
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
return cx16.r1
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ multiply_words .proc
|
||||
; but there currently is no way to use 4 consecutive bytes in ZP (without disabling irq and saving/restoring them)...
|
||||
|
||||
; mult62.a
|
||||
; from: https://github.com/TobyLobster/multiply_test/blob/main/tests/mult62.a
|
||||
; based on Dr Jefyll, http://forum.6502.org/viewtopic.php?f=9&t=689&start=0#p19958
|
||||
; - adjusted to use fixed zero page addresses
|
||||
; - removed 'decrement to avoid clc' as this is slower on average
|
||||
@@ -73,7 +74,7 @@ multiply_words .proc
|
||||
; - unrolled the two inner loops once
|
||||
;
|
||||
; 16 bit x 16 bit unsigned multiply, 32 bit result
|
||||
; Average cycles:
|
||||
; Average cycles: ~442 ?
|
||||
; 93 bytes
|
||||
|
||||
_multiplicand = P8ZP_SCRATCH_W2 ; 2 bytes
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Internal Math library routines - always included by the compiler
|
||||
; note: some functions you might expect here are builtin functions,
|
||||
; such as abs, sqrt, clamp, min, max for example.
|
||||
|
||||
math {
|
||||
%option no_symbol_prefixing
|
||||
@@ -140,6 +142,21 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub mul16_last_upper() -> uword @AY {
|
||||
; This routine peeks into the internal 32 bits multiplication result buffer of the
|
||||
; 16*16 bits multiplication routine, to fetch the upper 16 bits of the last calculation.
|
||||
; Notes:
|
||||
; - to avoid interference it's best to fetch and store this value immediately after the multiplication expression.
|
||||
; for instance, simply printing a number may already result in new multiplication calls being performed
|
||||
; - not all multiplications in the source code result in an actual multiplication call:
|
||||
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
|
||||
%asm {{
|
||||
lda multiply_words.result+2
|
||||
ldy multiply_words.result+3
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
|
||||
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
|
||||
cx16.r0L = 3 ; quadrant
|
||||
@@ -430,4 +447,41 @@ log2_tab
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub diff(ubyte v1 @A, ubyte v2 @Y) -> ubyte @A {
|
||||
; -- returns the (absolute) difference, or distance, between the two bytes
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_REG
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
eor #255
|
||||
inc a
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub diffw(uword w1 @R0, uword w2 @AY) -> uword @AY {
|
||||
; -- returns the (absolute) difference, or distance, between the two words
|
||||
%asm {{
|
||||
sec
|
||||
sbc cx16.r0L
|
||||
sta cx16.r0L
|
||||
tya
|
||||
sbc cx16.r0H
|
||||
sta cx16.r0H
|
||||
bcs +
|
||||
eor #255
|
||||
sta cx16.r0H
|
||||
lda cx16.r0L
|
||||
eor #255
|
||||
inc a
|
||||
sta cx16.r0L
|
||||
bne +
|
||||
inc cx16.r0H
|
||||
+ lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -297,6 +297,14 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
sub disable_caseswitch() {
|
||||
; PET doesn't have a key to swap case, so no-op
|
||||
}
|
||||
|
||||
sub enable_caseswitch() {
|
||||
; PET doesn't have a key to swap case, so no-op
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore PET
|
||||
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
; these work with PETSCII encoding instead.
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -28,6 +30,45 @@ sub spc() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tax
|
||||
clc
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_row() -> ubyte @X {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tya
|
||||
ldy #$00
|
||||
sta (colptr),y
|
||||
txa
|
||||
sta (rowptr),y
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character. color is ignored on PET
|
||||
%asm {{
|
||||
@@ -139,10 +180,10 @@ asmsub scroll_down () clobbers(A,X) {
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; ---- print null terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
@@ -328,7 +369,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
@@ -453,4 +495,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -146,11 +146,11 @@ _startloop dey
|
||||
iny
|
||||
bne -
|
||||
_notfound lda #0
|
||||
clc
|
||||
clc
|
||||
rts
|
||||
_found tya
|
||||
sec
|
||||
rts
|
||||
_found tya
|
||||
sec
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,31 @@ _found tya
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub append(uword target @R0, uword suffix @R1) clobbers(Y) -> ubyte @A {
|
||||
; Append the suffix string to the target. (make sure the buffer is large enough!)
|
||||
; Returns the length of the resulting string.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr length
|
||||
sty P8ZP_SCRATCH_B1
|
||||
tya
|
||||
clc
|
||||
adc cx16.r0
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda cx16.r0+1
|
||||
adc #0
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jsr prog8_lib.strcpy
|
||||
tya
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting.
|
||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Internal Math library routines - always included by the compiler
|
||||
; note: some functions you might expect here are builtin functions,
|
||||
; such as abs, sqrt, clamp, min, max for example.
|
||||
|
||||
math {
|
||||
|
||||
@@ -209,59 +211,85 @@ math {
|
||||
}
|
||||
}
|
||||
|
||||
sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
|
||||
; From a pair of positive coordinates, calculate discrete direction between 0 and 23 into A.
|
||||
; This adjusts the atan() result so that the direction N is centered on the angle=N instead of having it as a boundary
|
||||
ubyte angle = atan2(x1, y1, x2, y2) - 256/48
|
||||
return 23-lsb(mkword(angle,0) / 2730)
|
||||
}
|
||||
|
||||
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
|
||||
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
|
||||
; shift the points into the positive quadrant
|
||||
ubyte px1
|
||||
ubyte py1
|
||||
ubyte px2
|
||||
ubyte py2
|
||||
if x1<0 or x2<0 {
|
||||
px1 = x1 as ubyte + 128
|
||||
px2 = x2 as ubyte + 128
|
||||
} else {
|
||||
px1 = x1 as ubyte
|
||||
px2 = x2 as ubyte
|
||||
}
|
||||
if y1<0 or y2<0 {
|
||||
py1 = y1 as ubyte + 128
|
||||
py2 = y2 as ubyte + 128
|
||||
} else {
|
||||
py1 = y1 as ubyte
|
||||
py2 = y2 as ubyte
|
||||
sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
|
||||
; From a pair of positive coordinates, calculate discrete direction between 0 and 23 into A.
|
||||
; This adjusts the atan() result so that the direction N is centered on the angle=N instead of having it as a boundary
|
||||
ubyte angle = atan2(x1, y1, x2, y2) - 256/48
|
||||
return 23-lsb(mkword(angle,0) / 2730)
|
||||
}
|
||||
|
||||
return direction(px1, py1, px2, py2)
|
||||
}
|
||||
sub direction_sc(byte x1, byte y1, byte x2, byte y2) -> ubyte {
|
||||
; From a pair of signed coordinates around the origin, calculate discrete direction between 0 and 23 into A.
|
||||
; shift the points into the positive quadrant
|
||||
ubyte px1
|
||||
ubyte py1
|
||||
ubyte px2
|
||||
ubyte py2
|
||||
if x1<0 or x2<0 {
|
||||
px1 = x1 as ubyte + 128
|
||||
px2 = x2 as ubyte + 128
|
||||
} else {
|
||||
px1 = x1 as ubyte
|
||||
px2 = x2 as ubyte
|
||||
}
|
||||
if y1<0 or y2<0 {
|
||||
py1 = y1 as ubyte + 128
|
||||
py2 = y2 as ubyte + 128
|
||||
} else {
|
||||
py1 = y1 as ubyte
|
||||
py2 = y2 as ubyte
|
||||
}
|
||||
|
||||
sub direction_qd(ubyte quadrant, ubyte xdelta, ubyte ydelta) -> ubyte {
|
||||
; From a pair of X/Y deltas (both >=0), and quadrant 0-3, calculate discrete direction between 0 and 23.
|
||||
when quadrant {
|
||||
3 -> return direction(0, 0, xdelta, ydelta)
|
||||
2 -> return direction(xdelta, 0, 0, ydelta)
|
||||
1 -> return direction(0, ydelta, xdelta, 0)
|
||||
else -> return direction(xdelta, ydelta, 0, 0)
|
||||
return direction(px1, py1, px2, py2)
|
||||
}
|
||||
}
|
||||
|
||||
sub atan2(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
|
||||
;; Calculate the angle, in a 256-degree circle, between two points into A.
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
|
||||
%ir {{
|
||||
loadm.b r65532,math.atan2.x1
|
||||
loadm.b r65533,math.atan2.y1
|
||||
loadm.b r65534,math.atan2.x2
|
||||
loadm.b r65535,math.atan2.y2
|
||||
syscall 44 (r65532.b, r65533.b, r65534.b, r65535.b): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
sub direction_qd(ubyte quadrant, ubyte xdelta, ubyte ydelta) -> ubyte {
|
||||
; From a pair of X/Y deltas (both >=0), and quadrant 0-3, calculate discrete direction between 0 and 23.
|
||||
when quadrant {
|
||||
3 -> return direction(0, 0, xdelta, ydelta)
|
||||
2 -> return direction(xdelta, 0, 0, ydelta)
|
||||
1 -> return direction(0, ydelta, xdelta, 0)
|
||||
else -> return direction(xdelta, ydelta, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
sub atan2(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
|
||||
;; Calculate the angle, in a 256-degree circle, between two points into A.
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
|
||||
%ir {{
|
||||
loadm.b r65532,math.atan2.x1
|
||||
loadm.b r65533,math.atan2.y1
|
||||
loadm.b r65534,math.atan2.x2
|
||||
loadm.b r65535,math.atan2.y2
|
||||
syscall 44 (r65532.b, r65533.b, r65534.b, r65535.b): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub mul16_last_upper() -> uword {
|
||||
; This routine peeks into the internal 32 bits multiplication result buffer of the
|
||||
; 16*16 bits multiplication routine, to fetch the upper 16 bits of the last calculation.
|
||||
; Notes:
|
||||
; - to avoid interference it's best to fetch and store this value immediately after the multiplication expression.
|
||||
; for instance, simply printing a number may already result in new multiplication calls being performed
|
||||
; - not all multiplications in the source code result in an actual multiplication call:
|
||||
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
|
||||
%ir {{
|
||||
syscall 46 (): r0.w
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub diff(ubyte b1, ubyte b2) -> ubyte {
|
||||
if b1>b2
|
||||
return b1-b2
|
||||
return b2-b1
|
||||
}
|
||||
|
||||
sub diffw(uword w1, uword w2) -> uword {
|
||||
if w1>w2
|
||||
return w1-w2
|
||||
return w2-w1
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
; Monochrome Bitmap pixel graphics routines for the Virtual Machine
|
||||
; Using the full-screen 640x480 and 320x240 screen modes, but just black/white.
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
|
||||
|
||||
monogfx {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; read-only control variables:
|
||||
uword width = 0
|
||||
uword height = 0
|
||||
bool dont_stipple_flag = true ; set to false to enable stippling mode
|
||||
|
||||
sub lores() {
|
||||
; enable 320*240 bitmap mode
|
||||
sys.gfx_enable(0)
|
||||
width = 320
|
||||
height = 240
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub hires() {
|
||||
; enable 640*480 bitmap mode
|
||||
sys.gfx_enable(1)
|
||||
width = 640
|
||||
height = 480
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub textmode() {
|
||||
; back to normal text mode
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte color) {
|
||||
stipple(false)
|
||||
if color
|
||||
color=255
|
||||
sys.gfx_clear(color)
|
||||
}
|
||||
|
||||
sub stipple(bool enable) {
|
||||
dont_stipple_flag = not enable
|
||||
}
|
||||
|
||||
sub rect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
|
||||
if rwidth==0 or rheight==0
|
||||
return
|
||||
horizontal_line(xx, yy, rwidth, draw)
|
||||
if rheight==1
|
||||
return
|
||||
horizontal_line(xx, yy+rheight-1, rwidth, draw)
|
||||
vertical_line(xx, yy+1, rheight-2, draw)
|
||||
if rwidth==1
|
||||
return
|
||||
vertical_line(xx+rwidth-1, yy+1, rheight-2, draw)
|
||||
}
|
||||
|
||||
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
|
||||
; Draw a filled rectangle of the given size and color.
|
||||
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
||||
if rwidth==0
|
||||
return
|
||||
repeat rheight {
|
||||
horizontal_line(xx, yy, rwidth, draw)
|
||||
yy++
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword xx, uword yy, uword length, bool draw) {
|
||||
ubyte color = 0
|
||||
if draw
|
||||
color = 255
|
||||
uword xpos
|
||||
for xpos in xx to xx+length-1
|
||||
plot(xpos, yy, color)
|
||||
}
|
||||
|
||||
sub safe_horizontal_line(uword xx, uword yy, uword length, bool draw) {
|
||||
; does bounds checking and clipping
|
||||
if msb(yy)&$80!=0 or yy>=height
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
length += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=width
|
||||
return
|
||||
if xx+length>width
|
||||
length = width-xx
|
||||
if length>width
|
||||
return
|
||||
|
||||
horizontal_line(xx, yy, length, draw)
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
|
||||
ubyte color = 0
|
||||
if draw
|
||||
color = 255
|
||||
uword ypos
|
||||
for ypos in yy to yy+lheight-1
|
||||
plot(xx, ypos, color)
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, bool draw) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
cx16.r0 = x1
|
||||
x1 = x2
|
||||
x2 = cx16.r0
|
||||
cx16.r0 = y1
|
||||
y1 = y2
|
||||
y2 = cx16.r0
|
||||
}
|
||||
word @zp dx = (x2 as word)-x1
|
||||
word @zp dy = (y2 as word)-y1
|
||||
|
||||
if dx==0 {
|
||||
vertical_line(x1, y1, abs(dy) as uword +1, draw)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx) as uword +1, draw)
|
||||
return
|
||||
}
|
||||
|
||||
word @zp d = 0
|
||||
cx16.r1L = true ; 'positive_ix'
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
cx16.r1L = false
|
||||
}
|
||||
word @zp dx2 = dx*2
|
||||
word @zp dy2 = dy*2
|
||||
cx16.r14 = x1 ; internal plot X
|
||||
|
||||
if dx >= dy {
|
||||
if cx16.r1L {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14++
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14--
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if cx16.r1L {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14++
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14--
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, draw=draw
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
|
||||
; Does bounds checking and clipping.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, draw=draw
|
||||
safe_plot(cx16.r14, cx16.r15, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
|
||||
if draw {
|
||||
if dont_stipple_flag
|
||||
sys.gfx_plot(xx, yy, 255)
|
||||
else {
|
||||
if (xx ^ yy)&1
|
||||
sys.gfx_plot(xx, yy, 255)
|
||||
else
|
||||
sys.gfx_plot(xx, yy, 0)
|
||||
}
|
||||
}
|
||||
else
|
||||
sys.gfx_plot(xx, yy, 0)
|
||||
}
|
||||
|
||||
sub safe_plot(uword xx, uword yy, bool draw) {
|
||||
; A plot that does bounds checks to see if the pixel is inside the screen.
|
||||
if msb(xx)&$80!=0 or msb(yy)&$80!=0
|
||||
return
|
||||
if xx >= width or yy >= height
|
||||
return
|
||||
plot(xx, yy, draw)
|
||||
}
|
||||
|
||||
sub pget(uword @zp xx, uword yy) -> ubyte {
|
||||
return sys.gfx_getpixel(xx, yy)
|
||||
}
|
||||
|
||||
sub fill(uword x, uword y, bool draw) {
|
||||
; Non-recursive scanline flood fill.
|
||||
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
|
||||
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
|
||||
const ubyte MAXDEPTH = 64
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
word[MAXDEPTH] @split @shared stack_xr
|
||||
word[MAXDEPTH] @split @shared stack_y
|
||||
byte[MAXDEPTH] @shared stack_dy
|
||||
cx16.r12L = 0 ; stack pointer
|
||||
word x1
|
||||
word x2
|
||||
byte dy
|
||||
cx16.r10L = draw as ubyte
|
||||
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
|
||||
if cx16.r12L==MAXDEPTH
|
||||
return
|
||||
cx16.r0s = sy+sdy
|
||||
if cx16.r0s>=0 and cx16.r0s<=height-1 {
|
||||
stack_xl[cx16.r12L] = sxl
|
||||
stack_xr[cx16.r12L] = sxr
|
||||
stack_y[cx16.r12L] = sy
|
||||
stack_dy[cx16.r12L] = sdy
|
||||
cx16.r12L++
|
||||
}
|
||||
}
|
||||
sub pop_stack() {
|
||||
cx16.r12L--
|
||||
x1 = stack_xl[cx16.r12L]
|
||||
x2 = stack_xr[cx16.r12L]
|
||||
yy = stack_y[cx16.r12L]
|
||||
dy = stack_dy[cx16.r12L]
|
||||
yy+=dy
|
||||
}
|
||||
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
|
||||
if cx16.r11L == cx16.r10L
|
||||
return
|
||||
if xx<0 or xx>width-1 or yy<0 or yy>height-1
|
||||
return
|
||||
push_stack(xx, xx, yy, 1)
|
||||
push_stack(xx, xx, yy + 1, -1)
|
||||
word left = 0
|
||||
while cx16.r12L {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
while xx >= 0 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx--
|
||||
}
|
||||
if x1!=xx
|
||||
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
|
||||
else
|
||||
goto skip
|
||||
|
||||
left = xx + 1
|
||||
if left < x1
|
||||
push_stack(left, x1 - 1, yy, -dy)
|
||||
xx = x1 + 1
|
||||
|
||||
do {
|
||||
cx16.r9 = xx
|
||||
while xx <= width-1 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
if cx16.r9!=xx
|
||||
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
|
||||
|
||||
push_stack(left, xx - 1, yy, dy)
|
||||
if xx > x2 + 1
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
skip:
|
||||
xx++
|
||||
while xx <= x2 {
|
||||
if pget(xx as uword, yy as uword) == cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
left = xx
|
||||
} until xx>x2
|
||||
}
|
||||
}
|
||||
|
||||
sub text_charset(ubyte charset) {
|
||||
; -- select the text charset to use with the text() routine
|
||||
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
|
||||
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
|
||||
; TODO vm has no bitmap charset
|
||||
}
|
||||
|
||||
sub text(uword @zp xx, uword yy, bool draw, uword sctextptr) {
|
||||
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
||||
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||
; TODO vm has no bitmap charset
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,13 @@ string {
|
||||
}
|
||||
}
|
||||
|
||||
sub append(str target, str suffix) -> ubyte {
|
||||
; Append the suffix string to the target. (make sure the buffer is large enough!)
|
||||
; Returns the length of the resulting string.
|
||||
cx16.r0L = length(target)
|
||||
return copy(suffix, target+cx16.r0L) + cx16.r0L
|
||||
}
|
||||
|
||||
sub compare(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting.
|
||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||
|
||||
@@ -79,6 +79,13 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
sub disable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub enable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub gfx_enable(ubyte mode) {
|
||||
%ir {{
|
||||
|
||||
@@ -42,13 +42,14 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
||||
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform code optimizations")
|
||||
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
||||
val warnSymbolShadowing by cli.option(ArgType.Boolean, fullName = "warnshadow", description="show assembler warnings about symbol shadowing")
|
||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
val includeSourcelines by cli.option(ArgType.Boolean, fullName = "sourcelines", description = "include original Prog8 source lines in generated asm code")
|
||||
val splitWordArrays by cli.option(ArgType.Boolean, fullName = "splitarrays", description = "treat all word arrays as tagged with @split to make them lsb/msb split in memory")
|
||||
val breakpointCpuInstruction by cli.option(ArgType.Boolean, fullName = "breakinstr", description = "also use a CPU instruction for %breakpoint")
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${PETTarget.NAME}', '${VMTarget.NAME}') (required)")
|
||||
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
|
||||
@@ -122,6 +123,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
varsHighBank,
|
||||
compilationTarget!!,
|
||||
splitWordArrays == true,
|
||||
breakpointCpuInstruction = false,
|
||||
processedSymbols,
|
||||
srcdirs,
|
||||
outputPath
|
||||
@@ -190,6 +192,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
varsHighBank,
|
||||
compilationTarget!!,
|
||||
splitWordArrays == true,
|
||||
breakpointCpuInstruction == true,
|
||||
processedSymbols,
|
||||
srcdirs,
|
||||
outputPath
|
||||
|
||||
@@ -5,11 +5,11 @@ package prog8.buildversion
|
||||
*/
|
||||
const val MAVEN_GROUP = "prog8"
|
||||
const val MAVEN_NAME = "compiler"
|
||||
const val VERSION = "9.3-SNAPSHOT"
|
||||
const val GIT_REVISION = 4012
|
||||
const val GIT_SHA = "9167ba499de99696aa7c6887d4867eae70eb5caf"
|
||||
const val GIT_DATE = "2023-07-30T15:49:35Z"
|
||||
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 GIT_BRANCH = "master"
|
||||
const val BUILD_DATE = "2023-07-30T16:41:27Z"
|
||||
const val BUILD_UNIX_TIME = 1690735287514L
|
||||
const val BUILD_DATE = "2023-10-21T20:22:14Z"
|
||||
const val BUILD_UNIX_TIME = 1697919734197L
|
||||
const val DIRTY = 1
|
||||
@@ -38,6 +38,7 @@ class CompilerArguments(val filepath: Path,
|
||||
val varsHighBank: Int?,
|
||||
val compilationTarget: String,
|
||||
val splitWordArrays: Boolean,
|
||||
val breakpointCpuInstruction: Boolean,
|
||||
val symbolDefs: Map<String, String>,
|
||||
val sourceDirs: List<String> = emptyList(),
|
||||
val outputDir: Path = Path(""),
|
||||
@@ -74,6 +75,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
asmListfile = args.asmListfile
|
||||
includeSourcelines = args.includeSourcelines
|
||||
experimentalCodegen = args.experimentalCodegen
|
||||
breakpointCpuInstruction = args.breakpointCpuInstruction
|
||||
varsHighBank = args.varsHighBank
|
||||
splitWordArrays = args.splitWordArrays
|
||||
outputDir = args.outputDir.normalize()
|
||||
@@ -105,11 +107,12 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
|
||||
if (args.writeAssembly) {
|
||||
|
||||
// re-initialize memory areas with final compilationOptions
|
||||
compilationOptions.compTarget.machine.initializeMemoryAreas(compilationOptions)
|
||||
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
|
||||
args.errors.report()
|
||||
|
||||
val intermediateAst = IntermediateAstMaker(program).transform()
|
||||
val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
|
||||
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// printProgram(program)
|
||||
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
||||
@@ -254,6 +257,10 @@ fun parseMainModule(filepath: Path,
|
||||
importer.importImplicitLibraryModule("math")
|
||||
}
|
||||
importer.importImplicitLibraryModule("prog8_lib")
|
||||
if(program.allBlocks.any { it.options().any { option->option=="verafxmuls" } }) {
|
||||
if(compTarget.name==Cx16Target.NAME)
|
||||
importer.importImplicitLibraryModule("verafx")
|
||||
}
|
||||
|
||||
if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
|
||||
@@ -295,6 +302,14 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
.map { it[0].int!!..it[1].int!! }
|
||||
.toList()
|
||||
|
||||
val zpAllowed = toplevelModule.statements
|
||||
.asSequence()
|
||||
.filter { it is Directive && it.directive == "%zpallowed" }
|
||||
.map { (it as Directive).args }
|
||||
.filter { it.size==2 && it[0].int!=null && it[1].int!=null }
|
||||
.map { it[0].int!!..it[1].int!! }
|
||||
.toList()
|
||||
|
||||
val outputType = if (outputTypeStr == null) {
|
||||
if(compTarget is AtariTarget)
|
||||
OutputType.XEX
|
||||
@@ -324,7 +339,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
|
||||
return CompilationOptions(
|
||||
outputType, launcherType,
|
||||
zpType, zpReserved, floatsEnabled, noSysInit,
|
||||
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit,
|
||||
compTarget, 0u
|
||||
)
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class ModuleImporter(private val program: Program,
|
||||
|
||||
private fun removeDirectivesFromImportedModule(importedModule: Module) {
|
||||
// Most global directives don't apply for imported modules, so remove them
|
||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address")
|
||||
var directives = importedModule.statements.filterIsInstance<Directive>()
|
||||
importedModule.statements.removeAll(directives.toSet())
|
||||
directives = directives.filter{ it.directive !in moduleLevelDirectives }
|
||||
|
||||
@@ -7,6 +7,8 @@ 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
|
||||
import java.io.CharConversionException
|
||||
@@ -771,7 +773,7 @@ internal class AstChecker(private val program: Program,
|
||||
directive.args[0].name != "full")
|
||||
err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, dontuse, or full")
|
||||
}
|
||||
"%zpreserved" -> {
|
||||
"%zpreserved", "%zpallowed" -> {
|
||||
if(directive.parent !is Module)
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=2 || directive.args[0].int==null || directive.args[1].int==null)
|
||||
@@ -822,8 +824,16 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page", "merge", "splitarrays", "no_symbol_prefixing")}.any { !it })
|
||||
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page", "merge", "splitarrays", "no_symbol_prefixing", "verafxmuls")}.any { !it })
|
||||
err("invalid option directive argument(s)")
|
||||
if(directive.args.any {it.name=="align_word"} && directive.args.any { it.name=="align_page"})
|
||||
err("conflicting alignment options")
|
||||
if(directive.parent is Block) {
|
||||
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.args.any { it.name=="verafxmuls" } && compilerOptions.compTarget.name != Cx16Target.NAME)
|
||||
err("verafx option is only valid on cx16 target")
|
||||
}
|
||||
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
|
||||
}
|
||||
@@ -1183,8 +1193,13 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if(funcName[0] in InplaceModifyingBuiltinFunctions) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
if(funcName[0]=="setlsb" || funcName[0]=="setmsb") {
|
||||
val firstArg = functionCallStatement.args[0]
|
||||
if(firstArg !is IdentifierReference && firstArg !is ArrayIndexedExpression)
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
} else {
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead })
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1333,6 +1348,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(whenStmt.choices.isEmpty())
|
||||
errors.err("empty when statement", whenStmt.position)
|
||||
|
||||
if(whenStmt.condition.constValue(program)!=null)
|
||||
errors.warn("when-value is a constant and will always result in the same choice", whenStmt.condition.position)
|
||||
|
||||
super.visit(whenStmt)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ import prog8.ast.Program
|
||||
import prog8.ast.expressions.CharLiteral
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import java.io.CharConversionException
|
||||
|
||||
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
@@ -63,13 +65,35 @@ internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, err
|
||||
errors.err("compilation target doesn't support this text encoding", char.position)
|
||||
return noModifications
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
char,
|
||||
NumericLiteral(DataType.UBYTE, target.encodeString(char.value.toString(), char.encoding)[0].toDouble(), char.position),
|
||||
parent
|
||||
))
|
||||
return try {
|
||||
val encoded = target.encodeString(char.value.toString(), char.encoding)
|
||||
listOf(IAstModification.ReplaceNode(
|
||||
char,
|
||||
NumericLiteral(DataType.UBYTE, encoded[0].toDouble(), char.position),
|
||||
parent
|
||||
))
|
||||
} catch (x: CharConversionException) {
|
||||
errors.err(x.message ?: "can't encode character", char.position)
|
||||
noModifications
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
errors.err(x.message ?: "can't encode string", string.position)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
walker.visit(this)
|
||||
walker.applyModifications()
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.getTempVar
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.ContainmentCheck
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
@@ -39,72 +41,6 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// Try to replace A = B <operator> Something by A= B, A = A <operator> Something
|
||||
// this triggers the more efficent augmented assignment code generation more often.
|
||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||
|
||||
if(options.compTarget.name==VMTarget.NAME) // don't apply this optimization for Vm target
|
||||
return noModifications
|
||||
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
|
||||
if (binExpr != null && binExpr.operator !in ComparisonOperators) {
|
||||
if (binExpr.left !is BinaryExpression) {
|
||||
if (binExpr.right.referencesIdentifier(assignment.target.identifier!!.nameInSource)) {
|
||||
// the right part of the expression contains the target variable itself.
|
||||
// we can't 'split' it trivially because the variable will be changed halfway through.
|
||||
if(binExpr.operator in AssociativeOperators) {
|
||||
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||
// use the other part of the expression to split.
|
||||
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
|
||||
"unknown dt"
|
||||
)
|
||||
}, sourceDt, implicit=true)
|
||||
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.ASMGEN, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if(binExpr.left isSameAs assignment.target)
|
||||
return noModifications
|
||||
val typeCast = binExpr.left as? TypecastExpression
|
||||
if(typeCast!=null && typeCast.expression isSameAs assignment.target)
|
||||
return noModifications
|
||||
|
||||
if(binExpr.operator in "+-") {
|
||||
val leftDt = binExpr.left.inferType(program)
|
||||
val rightDt = binExpr.right.inferType(program)
|
||||
if(leftDt==rightDt && leftDt.isInteger && rightDt.isInteger && binExpr.right is ArrayIndexedExpression) {
|
||||
// don't split array[i] +/- array[i] (the codegen has an optimized path for this)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
|
||||
"unknown dt"
|
||||
)
|
||||
}, sourceDt, implicit=true)
|
||||
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.ASMGEN, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
|
||||
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
|
||||
@@ -221,35 +157,4 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(options.compTarget.name!=VMTarget.NAME) { // don't apply this optimization/check for Vm target
|
||||
val index = arrayIndexedExpression.indexer.indexExpr
|
||||
if (index !is NumericLiteral && index !is IdentifierReference) {
|
||||
// replace complex indexing expression with a temp variable to hold the computed index first
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val (tempVarName, _) = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
|
||||
val target = AssignTarget(IdentifierReference(tempVarName, expr.indexer.position), null, null, expr.indexer.position)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.ASMGEN, expr.indexer.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
|
||||
modifications.add(
|
||||
IAstModification.ReplaceNode(
|
||||
expr.indexer.indexExpr,
|
||||
target.identifier!!.copy(),
|
||||
expr.indexer
|
||||
)
|
||||
)
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(identifier, typecast.position),
|
||||
AddressOf(identifier, null, typecast.position),
|
||||
parent
|
||||
)
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
|
||||
// - 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
|
||||
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
@@ -211,4 +212,30 @@ _after:
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// for word variables:
|
||||
// @(&var) --> lsb(var)
|
||||
// @(&var+1) --> msb(var)
|
||||
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf?.arrayIndex!=null)
|
||||
return noModifications
|
||||
if(addrOf!=null && addrOf.identifier.inferType(program).isWords) {
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), memread.position), mutableListOf(addrOf.identifier), memread.position)
|
||||
return listOf(IAstModification.ReplaceNode(memread, lsb, parent))
|
||||
}
|
||||
val expr = memread.addressExpression as? BinaryExpression
|
||||
if(expr!=null && expr.operator=="+") {
|
||||
val addressOf = expr.left as? AddressOf
|
||||
val offset = (expr.right as? NumericLiteral)?.number?.toInt()
|
||||
if(addressOf!=null && offset==1) {
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), memread.position), mutableListOf(addressOf.identifier), memread.position)
|
||||
return listOf(IAstModification.ReplaceNode(memread, msb, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import kotlin.io.path.isRegularFile
|
||||
/**
|
||||
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
|
||||
*/
|
||||
class IntermediateAstMaker(private val program: Program) {
|
||||
class IntermediateAstMaker(private val program: Program, private val errors: IErrorReporter) {
|
||||
fun transform(): PtProgram {
|
||||
val ptProgram = PtProgram(
|
||||
program.name,
|
||||
@@ -161,6 +161,7 @@ class IntermediateAstMaker(private val program: Program) {
|
||||
private fun transform(srcBlock: Block): PtBlock {
|
||||
var alignment = PtBlock.BlockAlignment.NONE
|
||||
var forceOutput = false
|
||||
var veraFxMuls = false
|
||||
var noSymbolPrefixing = false
|
||||
val directives = srcBlock.statements.filterIsInstance<Directive>()
|
||||
for (directive in directives.filter { it.directive == "%option" }) {
|
||||
@@ -169,15 +170,16 @@ class IntermediateAstMaker(private val program: Program) {
|
||||
"align_word" -> alignment = PtBlock.BlockAlignment.WORD
|
||||
"align_page" -> alignment = PtBlock.BlockAlignment.PAGE
|
||||
"no_symbol_prefixing" -> noSymbolPrefixing = true
|
||||
"force_output" -> forceOutput=true
|
||||
"force_output" -> forceOutput = true
|
||||
"merge", "splitarrays" -> { /* ignore this one */ }
|
||||
"verafxmuls" -> veraFxMuls = true
|
||||
else -> throw FatalAstException("weird directive option: ${arg.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
|
||||
val src = srcBlock.definingModule.source
|
||||
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, noSymbolPrefixing, alignment, src, srcBlock.position)
|
||||
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, noSymbolPrefixing, veraFxMuls, alignment, src, srcBlock.position)
|
||||
makeScopeVarsDecls(vardecls).forEach { block.add(it) }
|
||||
for (stmt in statements)
|
||||
block.add(transformStatement(stmt))
|
||||
@@ -398,13 +400,26 @@ class IntermediateAstMaker(private val program: Program) {
|
||||
}
|
||||
|
||||
private fun transform(srcVar: VarDecl): PtNode {
|
||||
return when(srcVar.type) {
|
||||
when(srcVar.type) {
|
||||
VarDeclType.VAR -> {
|
||||
val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null
|
||||
PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||
if(srcVar.datatype in ArrayDatatypes) {
|
||||
if(value==null) {
|
||||
val blockOptions = srcVar.definingBlock.options()
|
||||
if("align_page" in blockOptions || "align_word" in blockOptions) {
|
||||
errors.warn("converting uninitialized array to explicit zeros because of block alignment option", srcVar.position)
|
||||
val zeros = PtArray(srcVar.datatype, srcVar.position)
|
||||
repeat(srcVar.arraysize!!.constIndex()!!) {
|
||||
zeros.children.add(PtNumber(ArrayToElementTypes.getValue(srcVar.datatype), 0.0, srcVar.position))
|
||||
}
|
||||
return PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, zeros, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
return PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||
}
|
||||
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
|
||||
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||
VarDeclType.CONST -> return PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
|
||||
VarDeclType.MEMORY -> return PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +455,8 @@ class IntermediateAstMaker(private val program: Program) {
|
||||
addr.add(PtIdentifier(name+"_lsb", dt, src.identifier.position)) // NOTE: assumes _lsb is first in memory! (immediately followed by _msb)
|
||||
else
|
||||
addr.add(transform(src.identifier))
|
||||
if(src.arrayIndex!=null)
|
||||
addr.add(transformExpression(src.arrayIndex!!.indexExpr))
|
||||
return addr
|
||||
}
|
||||
|
||||
|
||||
+9
-112
@@ -1,16 +1,17 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.AssignmentOrigin
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.IntegerDatatypes
|
||||
|
||||
internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val errors: IErrorReporter, val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
@@ -99,108 +100,4 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
override fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target
|
||||
return noModifications
|
||||
|
||||
val binExpr = ifElse.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in ComparisonOperators)
|
||||
return noModifications
|
||||
|
||||
// Simplify the conditional expression, introduce simple assignments if required.
|
||||
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
|
||||
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
|
||||
val simplify = simplifyConditionalExpression(binExpr)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(simplify.rightVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(
|
||||
ifElse,
|
||||
simplify.rightVarAssignment,
|
||||
parent as IStatementContainer
|
||||
)
|
||||
}
|
||||
if(simplify.leftVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(
|
||||
ifElse,
|
||||
simplify.leftVarAssignment,
|
||||
parent as IStatementContainer
|
||||
)
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
private class CondExprSimplificationResult(
|
||||
val leftVarAssignment: Assignment?,
|
||||
val leftOperandReplacement: Expression?,
|
||||
val rightVarAssignment: Assignment?,
|
||||
val rightOperandReplacement: Expression?
|
||||
)
|
||||
|
||||
private fun simplifyConditionalExpression(expr: BinaryExpression): CondExprSimplificationResult {
|
||||
|
||||
// NOTE: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
|
||||
|
||||
if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target
|
||||
return CondExprSimplificationResult(null, null, null, null)
|
||||
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
|
||||
if(!leftDt.isInteger || !rightDt.isInteger) {
|
||||
// we can't reasonably simplify non-integer expressions
|
||||
return CondExprSimplificationResult(null, null, null, null)
|
||||
}
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
var leftOperandReplacement: Expression? = null
|
||||
var rightAssignment: Assignment? = null
|
||||
var rightOperandReplacement: Expression? = null
|
||||
val separateLeftExpr = !expr.left.isSimple
|
||||
&& expr.left !is IFunctionCall
|
||||
&& expr.left !is ContainmentCheck
|
||||
val separateRightExpr = !expr.right.isSimple
|
||||
&& expr.right !is IFunctionCall
|
||||
&& expr.right !is ContainmentCheck
|
||||
|
||||
if(separateLeftExpr) {
|
||||
val name = getTempRegisterName(leftDt)
|
||||
leftOperandReplacement = IdentifierReference(name, expr.position)
|
||||
leftAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.left.copy(),
|
||||
AssignmentOrigin.ASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
if(separateRightExpr) {
|
||||
val (tempVarName, _) = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
|
||||
rightOperandReplacement = IdentifierReference(tempVarName, expr.position)
|
||||
rightAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position),
|
||||
expr.right.copy(),
|
||||
AssignmentOrigin.ASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
return CondExprSimplificationResult(
|
||||
leftAssignment, leftOperandReplacement,
|
||||
rightAssignment, rightOperandReplacement
|
||||
)
|
||||
}
|
||||
|
||||
fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
|
||||
return when {
|
||||
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
|
||||
dt istype DataType.UBYTE -> listOf("cx16", "r9L")
|
||||
dt istype DataType.BOOL -> listOf("cx16", "r9L")
|
||||
dt istype DataType.BYTE -> listOf("cx16", "r9sL")
|
||||
dt istype DataType.UWORD -> listOf("cx16", "r9")
|
||||
dt istype DataType.WORD -> listOf("cx16", "r9s")
|
||||
dt.isPassByReference -> listOf("cx16", "r9")
|
||||
else -> throw FatalAstException("invalid dt $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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", "%address", "%option")
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address", "%option")
|
||||
|
||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
@@ -52,10 +52,6 @@ internal class StatementReorderer(
|
||||
// This allows you to restart the program and have the same starting values of the variables
|
||||
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
|
||||
decl.value = null
|
||||
if(decl.name.startsWith("tempvar_") && decl.definingScope.name=="prog8_lib") {
|
||||
// no need to zero out the special internal temporary variables.
|
||||
return noModifications
|
||||
}
|
||||
if(decl.findInitializer(program)!=null)
|
||||
return noModifications // an initializer assignment for a vardecl is already here
|
||||
val nextFor = decl.nextSibling() as? ForLoop
|
||||
@@ -304,8 +300,8 @@ internal class StatementReorderer(
|
||||
val eltsize = program.memsizer.memorySize(ArrayToElementTypes.getValue(sourceVar.datatype))
|
||||
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
|
||||
mutableListOf(
|
||||
AddressOf(sourceIdent, assign.position),
|
||||
AddressOf(identifier, assign.position),
|
||||
AddressOf(sourceIdent, null, assign.position),
|
||||
AddressOf(identifier, null, assign.position),
|
||||
NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
|
||||
), false, assign.position
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.sign
|
||||
|
||||
|
||||
class TypecastsAdder(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : AstWalker() {
|
||||
@@ -245,96 +246,54 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
|
||||
// see if a typecast is needed to convert the arguments into the required parameter's type
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
when(val sub = call.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
sub.parameters.zip(call.args).forEach { (param, arg) ->
|
||||
val argItype = arg.inferType(program)
|
||||
if(argItype.isKnown) {
|
||||
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||
val requiredType = param.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
// don't need a cast for pass-by-reference types that are assigned to UWORD
|
||||
if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
|
||||
addTypecastOrCastedValueModification(modifications, arg, requiredType, call as Node)
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// We allow STR/ARRAY values in place of UWORD parameters.
|
||||
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
|
||||
val identifier = arg as? IdentifierReference
|
||||
if(identifier?.isSubroutineParameter(program)==false) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
AddressOf(identifier, arg.position),
|
||||
call as Node)
|
||||
}
|
||||
} else if(arg is NumericLiteral) {
|
||||
val cast = arg.cast(requiredType)
|
||||
if(cast.isValid)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
arg,
|
||||
cast.valueOrZero(),
|
||||
call as Node)
|
||||
} else if(requiredType==DataType.BOOL && argtype!=DataType.BOOL) {
|
||||
// cast to bool
|
||||
addTypecastOrCastedValueModification(modifications, arg, requiredType, call as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the argument is an identifier reference and the param is UWORD, add the missing &.
|
||||
if(arg is IdentifierReference && DataType.UWORD == param.type) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
arg,
|
||||
AddressOf(arg, arg.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(sub.name)
|
||||
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
if (argItype.isKnown) {
|
||||
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||
for (possibleType in pair.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
addTypecastOrCastedValueModification(modifications, pair.second, possibleType, call as Node)
|
||||
break
|
||||
}
|
||||
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
|
||||
// We allow STR/ARRAY values in place of UWORD parameters.
|
||||
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
|
||||
val identifier = pair.second as? IdentifierReference
|
||||
if(identifier?.isSubroutineParameter(program)==false) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
AddressOf(identifier, pair.second.position),
|
||||
call as Node)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the argument is an identifier reference and the param is UWORD, add the missing &.
|
||||
if(pair.second is IdentifierReference && DataType.UWORD in pair.first.possibleDatatypes) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
AddressOf(pair.second as IdentifierReference, pair.second.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> { }
|
||||
val sub = call.target.targetStatement(program)
|
||||
val params = when(sub) {
|
||||
is BuiltinFunctionPlaceholder -> BuiltinFunctions.getValue(sub.name).parameters
|
||||
is Subroutine -> sub.parameters.map { FParam(it.name, listOf(it.type).toTypedArray()) }
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
params.zip(call.args).forEach {
|
||||
val targetDt = it.first.possibleDatatypes.first()
|
||||
val argIdt = it.second.inferType(program)
|
||||
if (argIdt.isKnown) {
|
||||
val argDt = argIdt.getOr(DataType.UNDEFINED)
|
||||
if (argDt !in it.first.possibleDatatypes) {
|
||||
val identifier = it.second as? IdentifierReference
|
||||
val number = it.second as? NumericLiteral
|
||||
if(number!=null) {
|
||||
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
|
||||
} else if(identifier!=null && targetDt==DataType.UWORD && argDt in PassByReferenceDatatypes) {
|
||||
if(!identifier.isSubroutineParameter(program)) {
|
||||
// We allow STR/ARRAY values for UWORD parameters.
|
||||
// If it's an array (not STR), take the address.
|
||||
if(argDt != DataType.STR) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
AddressOf(identifier, null, it.second.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if(targetDt==DataType.BOOL) {
|
||||
addTypecastOrCastedValueModification(modifications, it.second, DataType.BOOL, call as Node)
|
||||
} else if(argDt isAssignableTo targetDt) {
|
||||
if(argDt!=DataType.STR || targetDt!=DataType.UWORD)
|
||||
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val identifier = it.second as? IdentifierReference
|
||||
if(identifier!=null && targetDt==DataType.UWORD) {
|
||||
// take the address of the identifier
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
AddressOf(identifier, null, it.second.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return modifications
|
||||
}
|
||||
|
||||
@@ -448,7 +407,11 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if(expressionToCast is NumericLiteral && expressionToCast.type!=DataType.FLOAT) { // refuse to automatically truncate floats
|
||||
val castedValue = expressionToCast.cast(requiredType)
|
||||
if (castedValue.isValid) {
|
||||
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
|
||||
val signOriginal = sign(expressionToCast.number)
|
||||
val signCasted = sign(castedValue.valueOrZero().number)
|
||||
if(signOriginal==signCasted) {
|
||||
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,24 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
errors.err("memory block '${slab.name}' already exists with a different size and/or alignment at ${other.position}", slab.position)
|
||||
}
|
||||
}
|
||||
|
||||
// remove unused strings from interned strings block
|
||||
val internedBlock = program.allBlocks.singleOrNull { it.name== internedStringsModuleName }
|
||||
internedBlock?.statements?.withIndex()?.reversed()?.forEach { (index, st) ->
|
||||
if(st is VarDecl && st.scopedName !in allStringRefs) {
|
||||
internedBlock.statements.removeAt(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
if(identifier.wasStringLiteral(program)) {
|
||||
allStringRefs.add(identifier.nameInSource)
|
||||
}
|
||||
}
|
||||
|
||||
private val allStringRefs = mutableListOf<List<String>>()
|
||||
|
||||
private class Slab(val name: String, val size: Int, val align: Int, val position: Position)
|
||||
private val memorySlabs = mutableListOf<Slab>()
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
varsHighBank = null,
|
||||
compilationTarget = target.name,
|
||||
splitWordArrays = false,
|
||||
breakpointCpuInstruction = false,
|
||||
symbolDefs = emptyMap(),
|
||||
outputDir = outputDir
|
||||
)
|
||||
@@ -95,6 +96,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
val onlyCx16 = cartesianProduct(
|
||||
listOf(
|
||||
"chunkedfile/demo",
|
||||
"vtui/testvtui",
|
||||
"pcmaudio/play-adpcm",
|
||||
"pcmaudio/stream-wav",
|
||||
@@ -121,6 +123,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"spotlight",
|
||||
"tehtriz",
|
||||
"testgfx2",
|
||||
"testmonogfx",
|
||||
),
|
||||
listOf(false, true)
|
||||
)
|
||||
|
||||
@@ -52,6 +52,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
varsHighBank = null,
|
||||
compilationTarget = Cx16Target.NAME,
|
||||
splitWordArrays = false,
|
||||
breakpointCpuInstruction = false,
|
||||
symbolDefs = emptyMap(),
|
||||
sourceDirs,
|
||||
outputDir
|
||||
|
||||
@@ -16,6 +16,7 @@ class TestGoldenRam: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.FULL,
|
||||
listOf((0x00u..0xffu)),
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = VMTarget(),
|
||||
|
||||
@@ -89,6 +89,17 @@ class TestStringEncodings: FunSpec({
|
||||
PetsciiEncoding.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||
}
|
||||
|
||||
test("testReturn") {
|
||||
PetsciiEncoding.encodePetscii("\r", true) shouldBe Ok(listOf<UByte>(13u))
|
||||
PetsciiEncoding.encodePetscii("\r", false) shouldBe Ok(listOf<UByte>(13u))
|
||||
PetsciiEncoding.encodePetscii("\n", true) shouldBe Ok(listOf<UByte>(13u))
|
||||
PetsciiEncoding.encodePetscii("\n", false) shouldBe Ok(listOf<UByte>(13u))
|
||||
PetsciiEncoding.decodePetscii(listOf(13u), false) shouldBe Ok("\n")
|
||||
PetsciiEncoding.decodePetscii(listOf(13u), true) shouldBe Ok("\n")
|
||||
PetsciiEncoding.decodePetscii(listOf(0x8du), false) shouldBe Ok("\r")
|
||||
PetsciiEncoding.decodePetscii(listOf(0x8du), true) shouldBe Ok("\r")
|
||||
}
|
||||
|
||||
test("testSpecialReplacements") {
|
||||
fun encodeP(c: Char, lower: Boolean) = PetsciiEncoding.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
|
||||
fun encodeS(c: Char, lower: Boolean) = PetsciiEncoding.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
|
||||
@@ -122,6 +133,10 @@ class TestStringEncodings: FunSpec({
|
||||
encodeP('\\', true) shouldBe 205u
|
||||
encodeS('\\', false) shouldBe 77u
|
||||
encodeS('\\', true) shouldBe 77u
|
||||
encodeP('\r', true) shouldBe 13u
|
||||
encodeP('\r', false) shouldBe 13u
|
||||
encodeP('\n', true) shouldBe 13u
|
||||
encodeP('\n', false) shouldBe 13u
|
||||
}
|
||||
|
||||
test("testBoxDrawingCharsEncoding") {
|
||||
|
||||
@@ -82,7 +82,28 @@ class TestSymbolTable: FunSpec({
|
||||
st.allMemMappedVariables.single().scopedName shouldBe "block1.sub1.v3"
|
||||
st.allMemorySlabs.single().scopedName shouldBe "block1.sub1.slab1"
|
||||
}
|
||||
|
||||
|
||||
test("static vars") {
|
||||
val node = PtIdentifier("dummy", DataType.UBYTE, Position.DUMMY)
|
||||
val stVar1 = StStaticVariable("initialized", DataType.UBYTE, 99.0, null, null, null, ZeropageWish.DONTCARE, node)
|
||||
val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, null, ZeropageWish.DONTCARE, node)
|
||||
val arrayInitNonzero = listOf(StArrayElement(1.1, null), StArrayElement(2.2, null), StArrayElement(3.3, null))
|
||||
val arrayInitAllzero = listOf(StArrayElement(0.0, null), StArrayElement(0.0, null), StArrayElement(0.0, null))
|
||||
val stVar3 = StStaticVariable("initialized", DataType.ARRAY_UW, null, null, arrayInitNonzero, 3, ZeropageWish.DONTCARE, node)
|
||||
val stVar4 = StStaticVariable("initialized", DataType.ARRAY_UW, null, null, arrayInitAllzero, 3, ZeropageWish.DONTCARE, node)
|
||||
val stVar5 = StStaticVariable("uninitialized", DataType.ARRAY_UW, null, null, null, 3, ZeropageWish.DONTCARE, node)
|
||||
|
||||
stVar1.uninitialized shouldBe false
|
||||
stVar1.length shouldBe null
|
||||
stVar2.uninitialized shouldBe true
|
||||
stVar2.length shouldBe null
|
||||
stVar3.uninitialized shouldBe false
|
||||
stVar3.length shouldBe 3
|
||||
stVar4.uninitialized shouldBe false
|
||||
stVar4.length shouldBe 3
|
||||
stVar5.uninitialized shouldBe true
|
||||
stVar5.length shouldBe 3
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -90,7 +111,7 @@ private fun makeSt(): SymbolTable {
|
||||
|
||||
// first build the AST
|
||||
val astProgram = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val astBlock1 = PtBlock("block1", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block1"), Position.DUMMY)
|
||||
val astBlock1 = PtBlock("block1", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block1"), Position.DUMMY)
|
||||
val astConstant1 = PtConstant("c1", DataType.UWORD, 12345.0, Position.DUMMY)
|
||||
val astConstant2 = PtConstant("blockc", DataType.UWORD, 999.0, Position.DUMMY)
|
||||
astBlock1.add(astConstant1)
|
||||
@@ -113,7 +134,7 @@ private fun makeSt(): SymbolTable {
|
||||
astBlock1.add(astSub2)
|
||||
val astBfunc = PtIdentifier("msb", DataType.UBYTE, Position.DUMMY)
|
||||
astBlock1.add(astBfunc)
|
||||
val astBlock2 = PtBlock("block2", null, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block2"), Position.DUMMY)
|
||||
val astBlock2 = PtBlock("block2", null, false, false, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("block2"), Position.DUMMY)
|
||||
val astSub21 = PtSub("sub1", emptyList(), null, Position.DUMMY)
|
||||
val astSub22 = PtSub("sub2", emptyList(), null, Position.DUMMY)
|
||||
val astSub221 = PtSub("subsub", emptyList(), null, Position.DUMMY)
|
||||
|
||||
@@ -58,11 +58,10 @@ class TestTypecasts: FunSpec({
|
||||
|
||||
val result2 = compileText(C64Target(), true, text, writeAssembly = true)!!
|
||||
val stmts2 = result2.compilerAst.entrypoint.statements
|
||||
stmts2.size shouldBe 6
|
||||
val expr2 = (stmts2[4] as Assignment).value as BinaryExpression
|
||||
stmts2.size shouldBe 5
|
||||
val expr2 = (stmts2[3] as Assignment).value as BinaryExpression
|
||||
expr2.operator shouldBe "&"
|
||||
expr2.right shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
|
||||
(expr2.left as IdentifierReference).nameInSource shouldBe listOf("bb")
|
||||
}
|
||||
|
||||
test("bool expressions with functioncalls") {
|
||||
@@ -740,7 +739,7 @@ main {
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
|
||||
result.compilerAst.entrypoint.statements.size shouldBe 15
|
||||
result.compilerAst.entrypoint.statements.size shouldBe 14
|
||||
}
|
||||
|
||||
test("invalid typecasts of numbers") {
|
||||
|
||||
@@ -33,6 +33,7 @@ class TestAbstractZeropage: FunSpec({
|
||||
free.addAll(0u..255u)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
@@ -47,6 +48,7 @@ class TestAbstractZeropage: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.FULL,
|
||||
listOf((0x50u..0x5fu)),
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = false,
|
||||
compTarget = DummyCompilationTarget,
|
||||
@@ -65,7 +67,7 @@ class TestC64Zeropage: FunSpec({
|
||||
val c64target = C64Target()
|
||||
|
||||
test("testNames") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(),
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = false,
|
||||
compTarget = c64target, loadAddress = 999u
|
||||
@@ -83,33 +85,33 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testZpFloatEnable") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
var result = zp.allocate("", DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled floats" }
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, c64target, 999u))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
result = zp2.allocate("", DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled ZP use" }
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target, 999u))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
zp3.allocate("", DataType.FLOAT, null, null, errors)
|
||||
}
|
||||
|
||||
test("testZpModesWithFloats") {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
}
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
}
|
||||
}
|
||||
|
||||
test("testZpDontuse") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
println(zp.free)
|
||||
zp.availableBytes() shouldBe 0
|
||||
val result = zp.allocate("", DataType.BYTE, null, null, errors)
|
||||
@@ -117,13 +119,13 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
zp1.availableBytes() shouldBe 18
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 88
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
|
||||
zp3.availableBytes() shouldBe 97
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
zp1.availableBytes() shouldBe 17
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 87
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp3.availableBytes() shouldBe 96
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp4.availableBytes() shouldBe 207
|
||||
zp4.allocate("test", DataType.UBYTE, null, null, errors)
|
||||
zp4.availableBytes() shouldBe 206
|
||||
@@ -132,7 +134,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp1.availableBytes() shouldBe 207
|
||||
4u shouldNotBeIn zp1.free
|
||||
35u shouldNotBeIn zp1.free
|
||||
@@ -143,7 +145,7 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldBeIn zp1.free
|
||||
255u shouldBeIn zp1.free
|
||||
199u shouldBeIn zp1.free
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, c64target, 999u))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 107
|
||||
4u shouldNotBeIn zp2.free
|
||||
35u shouldNotBeIn zp2.free
|
||||
@@ -154,15 +156,15 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldNotBeIn zp2.free
|
||||
255u shouldNotBeIn zp2.free
|
||||
199u shouldBeIn zp2.free
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), false, false, c64target, 999u))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 107
|
||||
4u shouldBeIn zp3.free
|
||||
35u shouldNotBeIn zp3.free
|
||||
}
|
||||
|
||||
test("testBasicsafeAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 18
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 17
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
|
||||
@@ -183,7 +185,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFullAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 207
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
@@ -214,25 +216,25 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testEfficientAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 18
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 17
|
||||
zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0au
|
||||
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9bu
|
||||
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x9eu
|
||||
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xa5u
|
||||
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xb0u
|
||||
zp.allocate("", DataType.UWORD, null, null, errors).getOrElse{throw it}.address shouldBe 0xbeu
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x0eu
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x92u
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x96u
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xa6u
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0xf9u
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
@@ -245,18 +247,18 @@ class TestCx16Zeropage: FunSpec({
|
||||
val cx16target = Cx16Target()
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, cx16target, 999u))
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, cx16target, 999u))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, cx16target, 999u))
|
||||
zp1.availableBytes() shouldBe 88
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, cx16target, 999u))
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
zp2.availableBytes() shouldBe 175
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
zp3.availableBytes() shouldBe 216
|
||||
zp3.allocate("test", DataType.UBYTE, null, null, errors)
|
||||
zp3.availableBytes() shouldBe 215
|
||||
@@ -265,7 +267,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
zp1.availableBytes() shouldBe 216
|
||||
0x22u shouldBeIn zp1.free
|
||||
0x80u shouldBeIn zp1.free
|
||||
@@ -275,7 +277,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("preallocated zp vars") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
zp1.allocatedVariables["test"] shouldBe null
|
||||
zp1.allocatedVariables["cx16.r0"] shouldNotBe null
|
||||
zp1.allocatedVariables["cx16.r15"] shouldNotBe null
|
||||
|
||||
@@ -8,6 +8,7 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.compiler.astprocessing.IntermediateAstMaker
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestIntermediateAst: FunSpec({
|
||||
@@ -26,8 +27,9 @@ class TestIntermediateAst: FunSpec({
|
||||
}
|
||||
"""
|
||||
val target = C64Target()
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = compileText(target, false, text, writeAssembly = false)!!
|
||||
val ast = IntermediateAstMaker(result.compilerAst).transform()
|
||||
val ast = IntermediateAstMaker(result.compilerAst, errors).transform()
|
||||
ast.name shouldBe result.compilerAst.name
|
||||
ast.allBlocks().any() shouldBe true
|
||||
val entry = ast.entrypoint() ?: fail("no main.start() found")
|
||||
|
||||
@@ -366,5 +366,18 @@ main {
|
||||
compileText(VMTarget(), optimize=false, src, writeAssembly=false, errors = errors) shouldBe null
|
||||
errors.errors.single() shouldContain "cannot use byte value"
|
||||
}
|
||||
|
||||
test("const eval of address-of a memory mapped variable") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
&ubyte mappedvar = 1000
|
||||
cx16.r0 = &mappedvar
|
||||
&ubyte[8] array = &mappedvar
|
||||
cx16.r0 = &array
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class TestArrayThings: FunSpec({
|
||||
test("assign prefix var to array should compile fine and is not split into inplace array modification") {
|
||||
@@ -147,5 +148,32 @@ main {
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("array target with expression for index") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] array = [1,2,3]
|
||||
array[cx16.r0L+1] += 42
|
||||
cx16.r0L = array[cx16.r0L+1]
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("split array in zeropage is okay") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
uword[3] @zp @split @shared thearray
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
|
||||
val assemblyFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
|
||||
val assembly = assemblyFile.readText()
|
||||
assembly shouldContain "thearray_lsb"
|
||||
assembly shouldContain "thearray_msb"
|
||||
}
|
||||
})
|
||||
|
||||
@@ -52,13 +52,13 @@ class TestAsmGenSymbols: StringSpec({
|
||||
|
||||
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
|
||||
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)
|
||||
@@ -74,8 +74,8 @@ class TestAsmGenSymbols: StringSpec({
|
||||
|
||||
fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
|
||||
val ptProgram = IntermediateAstMaker(program).transform()
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, C64Target(), 999u)
|
||||
val ptProgram = IntermediateAstMaker(program, errors).transform()
|
||||
val st = SymbolTableMaker(ptProgram, options).make()
|
||||
return AsmGen6502Internal(ptProgram, st, options, errors)
|
||||
}
|
||||
|
||||
Loaded 100 of 170 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user