mirror of
https://github.com/irmen/prog8.git
synced 2026-10-23 12:02:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6516d7cb15 | ||
|
|
31cf76042d | ||
|
|
c4c4dcf2b3 | ||
|
|
03145630f8 | ||
|
|
e2fcac322f | ||
|
|
beaff4d650 | ||
|
|
79af96ddde | ||
|
|
e439720c9d | ||
|
|
48d0185ea4 | ||
|
|
e2592b4e0b | ||
|
|
2967866e3d | ||
|
|
b566ea5c3f | ||
|
|
8f6eaeac2c | ||
|
|
b4facaeb3c | ||
|
|
d12b7ccc6b | ||
|
|
453e8bd0a0 | ||
|
|
9204d390ae | ||
|
|
b70ce0015c | ||
|
|
d113827753 | ||
|
|
c67f877857 | ||
|
|
0ec719e429 | ||
|
|
17f7b11148 | ||
|
|
966b017670 | ||
|
|
4c98070b3c | ||
|
|
3681d6ee1c | ||
|
|
0af17cdc33 | ||
|
|
2aae1f5e30 | ||
|
|
d18f2a7bfd | ||
|
|
9046fe8d3a | ||
|
|
78c7ee247a | ||
|
|
d5adb85e5b | ||
|
|
69f953fd9b | ||
|
|
484677b4b1 | ||
|
|
b10a8e728f | ||
|
|
25f25a8767 | ||
|
|
0c053e4a2c | ||
|
|
3f6521cc9b | ||
|
|
a074491d5b | ||
|
|
a291164953 | ||
|
|
43c55b58d2 | ||
|
|
e7298f8162 | ||
|
|
ddf990296b | ||
|
|
ead8aa7800 | ||
|
|
7a9dd1ac9b | ||
|
|
1c97c22eff | ||
|
|
bbf621a8c4 | ||
|
|
8efa89165c | ||
|
|
4f8aaf9244 | ||
|
|
a97edef380 | ||
|
|
eefae24aa3 | ||
|
|
54bffc91ae | ||
|
|
63f5ef9e14 | ||
|
|
034f27a8dd | ||
|
|
c2f6311367 | ||
|
|
6f00a48772 | ||
|
|
b3dba67405 | ||
|
|
c9a4235669 | ||
|
|
ae0d52274c | ||
|
|
8973763866 | ||
|
|
3d799ae7fe |
No files matched your search
Generated
+10
-10
@@ -1,23 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24" />
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.24/kotlin-stdlib-jdk8-1.9.24.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.24/kotlin-stdlib-1.9.24.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.24/kotlin-stdlib-jdk7-1.9.24.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.24/kotlin-stdlib-jdk8-1.9.24-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.24/kotlin-stdlib-1.9.24-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.24/kotlin-stdlib-jdk7-1.9.24-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.24/kotlin-stdlib-jdk8-1.9.24-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.24/kotlin-stdlib-1.9.24-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.24/kotlin-stdlib-jdk7-1.9.24-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.1">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.2">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
|
||||
+5
-5
@@ -1,17 +1,17 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.9.0" />
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.0/kotest-assertions-core-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.0/kotest-assertions-shared-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.0/kotest-common-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.0/kotest-assertions-api-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
|
||||
+11
-11
@@ -1,14 +1,14 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.0" />
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.0/kotest-runner-junit5-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.0/kotest-framework-api-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.0/kotest-assertions-shared-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.0/kotest-common-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.0/kotest-framework-engine-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||
@@ -18,12 +18,12 @@
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.0/kotest-framework-discovery-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.0/kotest-assertions-core-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.0/kotest-assertions-api-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.0/kotest-extensions-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.0/kotest-framework-concurrency-jvm-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
|
||||
|
||||
@@ -53,6 +53,7 @@ What does Prog8 provide?
|
||||
|
||||
- all advantages of a higher level language over having to write assembly code manually
|
||||
- programs run very fast because compilation to native machine code
|
||||
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
|
||||
- modularity, symbol scoping, subroutines
|
||||
- various data types other than just bytes (16-bit words, floats, strings)
|
||||
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
|
||||
@@ -66,7 +67,7 @@ What does Prog8 provide?
|
||||
- conditional branches
|
||||
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
|
||||
- ``in`` expression for concise and efficient multi-value/containment check
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
|
||||
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
|
||||
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
|
||||
@@ -222,7 +222,7 @@ class StMemVar(name: String,
|
||||
init{
|
||||
require(dt!=DataType.BOOL && dt!=DataType.ARRAY_BOOL)
|
||||
if(dt in ArrayDatatypes || dt == DataType.STR)
|
||||
require(length!=null) { "memory mapped array or string must have known length" }
|
||||
requireNotNull(length)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -267,12 +267,12 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
|
||||
override fun hashCode(): Int = Objects.hash(type, number)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is PtNumber)
|
||||
return false
|
||||
return if(other==null || other !is PtNumber)
|
||||
false
|
||||
else if(type!=DataType.BOOL && other.type!=DataType.BOOL)
|
||||
return number==other.number
|
||||
number==other.number
|
||||
else
|
||||
return type==other.type && number==other.number
|
||||
type==other.type && number==other.number
|
||||
}
|
||||
|
||||
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
|
||||
|
||||
@@ -76,14 +76,14 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
// cmp returns a status in the carry flag, but not a proper return value
|
||||
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
||||
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
|
||||
"prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null),
|
||||
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
|
||||
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
|
||||
"prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
|
||||
"prog8_ifelse_bittest_notset" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
|
||||
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null),
|
||||
"abs__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE),
|
||||
"abs__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD),
|
||||
@@ -99,8 +99,6 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"divmod" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divisor", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("quotient", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"divmod__ubyte" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE)), FParam("divisor", arrayOf(DataType.UBYTE)), FParam("quotient", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
|
||||
"divmod__uword" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UWORD)), FParam("divisor", arrayOf(DataType.UWORD)), FParam("quotient", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
|
||||
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
|
||||
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
|
||||
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
|
||||
@@ -136,6 +134,5 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
val InplaceModifyingBuiltinFunctions = setOf(
|
||||
"setlsb", "setmsb",
|
||||
"rol", "ror", "rol2", "ror2",
|
||||
"sort", "reverse",
|
||||
"divmod", "divmod__ubyte", "divmod__uword"
|
||||
)
|
||||
@@ -1,6 +1,11 @@
|
||||
package prog8.code.core
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
val powersOfTwoFloat = (0..16).map { (2.0).pow(it) }.toTypedArray()
|
||||
val negativePowersOfTwoFloat = powersOfTwoFloat.map { -it }.toTypedArray()
|
||||
val powersOfTwoInt = (0..16).map { 2.0.pow(it).toInt() }.toTypedArray()
|
||||
|
||||
fun Number.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
|
||||
@@ -29,7 +29,6 @@ interface IMachineDefinition {
|
||||
fun convertFloatToBytes(num: Double): List<UByte>
|
||||
fun convertBytesToFloat(bytes: List<UByte>): Double
|
||||
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
fun isIOAddress(address: UInt): Boolean
|
||||
}
|
||||
@@ -8,7 +8,8 @@ enum class Encoding(val prefix: String) {
|
||||
ISO("iso"), // cx16 (iso-8859-15)
|
||||
ISO5("iso5"), // cx16 (iso-8859-5, cyrillic)
|
||||
ISO16("iso16"), // cx16 (iso-8859-16, eastern european)
|
||||
CP437("cp437") // cx16 (ibm pc, codepage 437)
|
||||
CP437("cp437"), // cx16 (ibm pc, codepage 437)
|
||||
KATAKANA("kata") // cx16 (katakana)
|
||||
}
|
||||
|
||||
interface IStringEncoding {
|
||||
|
||||
@@ -11,6 +11,7 @@ fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st:
|
||||
return
|
||||
while (errors.noErrors() &&
|
||||
(optimizeCommonSubExpressions(program, errors)
|
||||
+ optimizeBitTest(program, options)
|
||||
+ optimizeAssignTargets(program, st, errors)) > 0
|
||||
) {
|
||||
// keep rolling
|
||||
@@ -164,6 +165,51 @@ private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: I
|
||||
return changes
|
||||
}
|
||||
|
||||
|
||||
private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): Int {
|
||||
if(options.compTarget.machine.cpu == CpuType.VIRTUAL)
|
||||
return 0 // the special bittest optimization is not yet valid for the IR
|
||||
|
||||
var changes = 0
|
||||
var recurse = true
|
||||
walkAst(program) { node: PtNode, depth: Int ->
|
||||
if(node is PtIfElse) {
|
||||
val condition = node.condition as? PtBinaryExpression
|
||||
if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) {
|
||||
if(condition.right.asConstInteger()==0) {
|
||||
val and = condition.left as? PtBinaryExpression
|
||||
if(and != null && and.operator=="&" && and.type == DataType.UBYTE) {
|
||||
val variable = and.left as? PtIdentifier
|
||||
val bitmask = and.right.asConstInteger()
|
||||
if(variable!=null && variable.type in ByteDatatypes && (bitmask==128 || bitmask==64)) {
|
||||
val setOrNot = if(condition.operator=="!=") "set" else "notset"
|
||||
val index = node.parent.children.indexOf(node)
|
||||
val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, node.condition.position)
|
||||
bittestCall.add(variable)
|
||||
if(bitmask==128)
|
||||
bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position))
|
||||
else
|
||||
bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position))
|
||||
val ifElse = PtIfElse(node.position)
|
||||
ifElse.add(bittestCall)
|
||||
ifElse.add(node.ifScope)
|
||||
if(node.hasElse())
|
||||
ifElse.add(node.elseScope)
|
||||
node.parent.children[index] = ifElse
|
||||
ifElse.parent = node.parent
|
||||
changes++
|
||||
recurse = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
recurse
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
|
||||
internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister: RegisterOrPair): Boolean {
|
||||
if(returnedRegister in Cx16VirtualRegisters) {
|
||||
val regname = returnedRegister.name.lowercase()
|
||||
|
||||
@@ -19,6 +19,7 @@ object Encoder: IStringEncoding {
|
||||
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str)
|
||||
Encoding.ISO16 -> IsoEasternEncoding.encode(str)
|
||||
Encoding.CP437 -> Cp437Encoding.encode(str)
|
||||
Encoding.KATAKANA -> KatakanaEncoding.encode(str)
|
||||
else -> throw InternalCompilerException("unsupported encoding $encoding")
|
||||
}
|
||||
return coded.fold(
|
||||
@@ -35,6 +36,7 @@ object Encoder: IStringEncoding {
|
||||
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes)
|
||||
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes)
|
||||
Encoding.CP437 -> Cp437Encoding.decode(bytes)
|
||||
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes)
|
||||
else -> throw InternalCompilerException("unsupported encoding $encoding")
|
||||
}
|
||||
return decoded.fold(
|
||||
|
||||
@@ -25,13 +25,6 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes")
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float")
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.output == OutputType.XEX)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
val emulatorName: String
|
||||
val cmdline: List<String>
|
||||
|
||||
@@ -36,13 +36,6 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The c128 target only supports the main emulator (Vice).")
|
||||
|
||||
@@ -37,13 +37,6 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The c64 target only supports the main emulator (Vice).")
|
||||
|
||||
@@ -36,13 +36,6 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
val emulator: String
|
||||
val extraArgs: List<String>
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package prog8.code.target.encodings
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import java.io.CharConversionException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
|
||||
object JapaneseCharacterConverter {
|
||||
// adapted from https://github.com/raminduw/Japanese-Character-Converter
|
||||
|
||||
private val ZENKAKU_KATAKANA = charArrayOf(
|
||||
'ァ', 'ア', 'ィ', 'イ', 'ゥ',
|
||||
'ウ', 'ェ', 'エ', 'ォ', 'オ', 'カ', 'ガ', 'キ', 'ギ', 'ク', 'グ', 'ケ', 'ゲ',
|
||||
'コ', 'ゴ', 'サ', 'ザ', 'シ', 'ジ', 'ス', 'ズ', 'セ', 'ゼ', 'ソ', 'ゾ', 'タ',
|
||||
'ダ', 'チ', 'ヂ', 'ッ', 'ツ', 'ヅ', 'テ', 'デ', 'ト', 'ド', 'ナ', 'ニ', 'ヌ',
|
||||
'ネ', 'ノ', 'ハ', 'バ', 'パ', 'ヒ', 'ビ', 'ピ', 'フ', 'ブ', 'プ', 'ヘ', 'ベ',
|
||||
'ペ', 'ホ', 'ボ', 'ポ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ャ', 'ヤ', 'ュ', 'ユ',
|
||||
'ョ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ヮ', 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン',
|
||||
'ヴ', 'ヵ', 'ヶ'
|
||||
)
|
||||
|
||||
private val HANKAKU_HIRAGANA = charArrayOf(
|
||||
'ぁ', 'あ', 'ぃ', 'い', 'ぅ', 'う', 'ぇ', 'え',
|
||||
'ぉ', 'お', 'か', 'が', 'き', 'ぎ', 'く', 'ぐ',
|
||||
'け', 'げ', 'こ', 'ご', 'さ', 'ざ', 'し', 'じ',
|
||||
'す', 'ず', 'せ', 'ぜ', 'そ', 'ぞ', 'た', 'だ',
|
||||
'ち', 'ぢ', 'っ', 'つ', 'づ', 'て', 'で', 'と',
|
||||
'ど', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ば',
|
||||
'ぱ', 'ひ', 'び', 'ぴ', 'ふ', 'ぶ', 'ぷ', 'へ',
|
||||
'べ', 'ぺ', 'ほ', 'ぼ', 'ぽ', 'ま', 'み', 'む',
|
||||
'め', 'も', 'ゃ', 'や', 'ゅ', 'ゆ', 'ょ', 'よ',
|
||||
'ら', 'り', 'る', 'れ', 'ろ', 'ゎ', 'わ', 'ゐ',
|
||||
'ゑ', 'を', 'ん', 'ゔ', 'ゕ', 'ゖ'
|
||||
)
|
||||
|
||||
private val HANKAKU_KATAKANA = arrayOf(
|
||||
"ァ", "ア", "ィ", "イ", "ゥ",
|
||||
"ウ", "ェ", "エ", "ォ", "オ", "カ", "ガ", "キ", "ギ", "ク", "グ", "ケ",
|
||||
"ゲ", "コ", "ゴ", "サ", "ザ", "シ", "ジ", "ス", "ズ", "セ", "ゼ", "ソ",
|
||||
"ゾ", "タ", "ダ", "チ", "ヂ", "ッ", "ツ", "ヅ", "テ", "デ", "ト", "ド",
|
||||
"ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "バ", "パ", "ヒ", "ビ", "ピ", "フ",
|
||||
"ブ", "プ", "ヘ", "ベ", "ペ", "ホ", "ボ", "ポ", "マ", "ミ", "ム", "メ",
|
||||
"モ", "ャ", "ヤ", "ュ", "ユ", "ョ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ",
|
||||
"ワ", "イ", "エ", "ヲ", "ン", "ヴ", "カ", "ケ"
|
||||
)
|
||||
|
||||
private val ZENKAKU_KATAKANA_FIRST_CHAR_CODE = ZENKAKU_KATAKANA.first().code
|
||||
private val HANKAKU_HIRAGANA_FIRST_CHAR_CODE = HANKAKU_HIRAGANA.first().code
|
||||
|
||||
private fun zenkakuKatakanaToHankakuKatakana(c: Char): String = if (c in ZENKAKU_KATAKANA) HANKAKU_KATAKANA[c.code - ZENKAKU_KATAKANA_FIRST_CHAR_CODE] else c.toString()
|
||||
private fun hankakuKatakanaToZenkakuKatakana(c: Char): Char = if (c in HANKAKU_HIRAGANA) ZENKAKU_KATAKANA[c.code - HANKAKU_HIRAGANA_FIRST_CHAR_CODE] else c
|
||||
|
||||
fun zenkakuKatakanaToHankakuKatakana(s: String): String = buildString {
|
||||
for (element in s) {
|
||||
val converted = hankakuKatakanaToZenkakuKatakana(element)
|
||||
val convertedChar = zenkakuKatakanaToHankakuKatakana(converted)
|
||||
append(convertedChar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object KatakanaEncoding {
|
||||
val charset: Charset = Charset.forName("JIS_X0201")
|
||||
|
||||
fun encode(str: String): Result<List<UByte>, CharConversionException> {
|
||||
return try {
|
||||
val mapped = str.map { chr ->
|
||||
when (chr) {
|
||||
|
||||
'\u0000' -> 0u
|
||||
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves
|
||||
|
||||
'♥' -> 0xe3u
|
||||
'♦' -> 0xe4u
|
||||
'♣' -> 0xe5u
|
||||
'♠' -> 0xe6u
|
||||
|
||||
'大' -> 0xeau
|
||||
'中' -> 0xebu
|
||||
'小' -> 0xecu
|
||||
'百' -> 0xedu
|
||||
'千' -> 0xeeu
|
||||
'万' -> 0xefu
|
||||
'♪' -> 0xf0u
|
||||
'土' -> 0xf1u
|
||||
'金' -> 0xf2u
|
||||
'木' -> 0xf3u
|
||||
'水' -> 0xf4u
|
||||
'火' -> 0xf5u
|
||||
'月' -> 0xf6u
|
||||
'日' -> 0xf7u
|
||||
'時' -> 0xf8u
|
||||
'分' -> 0xf9u
|
||||
'秒' -> 0xfau
|
||||
'年' -> 0xfbu
|
||||
'円' -> 0xfcu
|
||||
'人' -> 0xfdu
|
||||
'生' -> 0xfeu
|
||||
'〒' -> 0xffu
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(chr.code - 0x8000).toUByte()
|
||||
}
|
||||
else -> charset.encode(chr.toString())[0].toUByte()
|
||||
}
|
||||
}
|
||||
Ok(mapped)
|
||||
} catch (ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
|
||||
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
|
||||
return try {
|
||||
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
|
||||
} catch (ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,13 +36,6 @@ class PETMachineDefinition: IMachineDefinition {
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The pet target only supports the main emulator (Vice).")
|
||||
|
||||
@@ -50,10 +50,6 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7)
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return listOf("syslib")
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
println("\nStarting Virtual Machine...")
|
||||
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
|
||||
|
||||
@@ -27,7 +27,7 @@ dependencies {
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
@@ -50,6 +50,13 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeTSBtoRegularOr(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
var linesByFourteen = getLinesBy(lines, 14)
|
||||
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
|
||||
if(mods.isNotEmpty()) {
|
||||
@@ -384,6 +391,7 @@ private fun optimizeStoreLoadSame(
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[1].value.trimStart()
|
||||
val second = lines[2].value.trimStart()
|
||||
val third = lines[3].value.trimStart()
|
||||
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
|
||||
@@ -393,7 +401,6 @@ private fun optimizeStoreLoadSame(
|
||||
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("ldx ") && second.startsWith("ldx "))
|
||||
) {
|
||||
val third = lines[3].value.trimStart()
|
||||
val attemptRemove =
|
||||
if(third.isBranch()) {
|
||||
// a branch instruction follows, we can only remove the load instruction if
|
||||
@@ -508,6 +515,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[0].value
|
||||
val second = lines[1].value
|
||||
val third = lines[2].value
|
||||
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
@@ -577,6 +585,58 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
|
||||
mods += Modification(lines[3].index, true, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun sameLabel(branchInstr: String, jumpInstr: String, labelInstr: String): Boolean {
|
||||
if('(' in jumpInstr) return false // indirect jump cannot be replaced
|
||||
val label = labelInstr.trimEnd().substringBefore(':').substringBefore(' ').substringBefore('\t')
|
||||
val branchLabel = branchInstr.trimStart().substring(3).trim()
|
||||
return label==branchLabel
|
||||
}
|
||||
|
||||
// beq Label + jmp Addr + Label -> bne Addr
|
||||
if((" jmp" in second || "\tjmp " in second) && haslabel(third)) {
|
||||
if((" beq " in first || "\tbeq " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bne")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bne " in first || "\tbne " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "beq")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bcc " in first || "\tbcc " in first) && sameLabel(first, second, third)){
|
||||
val branch = second.replace("jmp", "bcs")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bcs " in first || "\tbcs " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bcc")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bpl " in first || "\tbpl " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bmi")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bmi " in first || "\tbmi " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bpl")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bvc " in first || "\tbvc " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bvs")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
else if((" bvs " in first || "\tbvs " in first) && sameLabel(first, second, third)) {
|
||||
val branch = second.replace("jmp", "bvc")
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
@@ -614,6 +674,52 @@ private fun optimizeUselessPushPopStack(linesByFour: Sequence<List<IndexedValue<
|
||||
optimize('a', lines)
|
||||
optimize('x', lines)
|
||||
optimize('y', lines)
|
||||
|
||||
val first = lines[1].value.trimStart()
|
||||
val second = lines[2].value.trimStart()
|
||||
val third = lines[3].value.trimStart()
|
||||
|
||||
// phy + ldy + pla -> tya + ldy
|
||||
// phx + ldx + pla -> txa + ldx
|
||||
// pha + lda + pla -> nop
|
||||
if(first=="phy" && second.startsWith("ldy ") && third=="pla") {
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, " tya"))
|
||||
}
|
||||
else if(first=="phx" && second.startsWith("ldx ") && third=="pla") {
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, " txa"))
|
||||
}
|
||||
else if(first=="pha" && second.startsWith("lda ") && third=="pla") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
|
||||
private fun optimizeTSBtoRegularOr(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
|
||||
// Asm peephole: lda var2 / tsb var1 / lda var1 Replace this with this to save 1 cycle: lda var1 / ora var2 / sta var1
|
||||
val mods = mutableListOf<Modification>()
|
||||
|
||||
for(lines in linesByFour) {
|
||||
val first = lines[0].value.trimStart()
|
||||
val second = lines[1].value.trimStart()
|
||||
val third = lines[2].value.trimStart()
|
||||
if(first.startsWith("lda") && second.startsWith("tsb") && third.startsWith("lda")) {
|
||||
val operand1 = first.substring(3)
|
||||
val operand2 = second.substring(3)
|
||||
val operand3 = third.substring(3)
|
||||
if(operand1!=operand2 && operand2==operand3) {
|
||||
mods.add(Modification(lines[0].index, false, " lda $operand2"))
|
||||
mods.add(Modification(lines[1].index, false, " ora $operand1"))
|
||||
mods.add(Modification(lines[2].index, false, " sta $operand2"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(fcall, resultRegister)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(fcall, resultRegister)
|
||||
"abs__byte", "abs__word", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, resultRegister, sscope)
|
||||
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(fcall, resultRegister, sscope)
|
||||
"divmod__ubyte" -> funcDivmod(fcall)
|
||||
@@ -44,8 +43,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"ror2" -> funcRor2(fcall)
|
||||
"setlsb" -> funcSetLsbMsb(fcall, false)
|
||||
"setmsb" -> funcSetLsbMsb(fcall, true)
|
||||
"sort" -> funcSort(fcall)
|
||||
"reverse" -> funcReverse(fcall)
|
||||
"memory" -> funcMemory(fcall, discardResult, resultRegister)
|
||||
"peekw" -> funcPeekW(fcall, resultRegister)
|
||||
"peekf" -> funcPeekF(fcall, resultRegister)
|
||||
@@ -68,6 +65,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"cmp" -> funcCmp(fcall)
|
||||
"callfar" -> funcCallFar(fcall, resultRegister)
|
||||
"call" -> funcCall(fcall)
|
||||
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
|
||||
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
|
||||
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
|
||||
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
|
||||
@@ -403,115 +402,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcReverse(fcall: PtBuiltinFunctionCall) {
|
||||
val variable = fcall.args.single() as PtIdentifier
|
||||
val symbol = asmgen.symbolTable.lookup(variable.name)
|
||||
val (dt, numElements) = when(symbol) {
|
||||
is StStaticVariable -> symbol.dt to symbol.length!!
|
||||
is StMemVar -> symbol.dt to symbol.length!!
|
||||
else -> DataType.UNDEFINED to 0
|
||||
}
|
||||
val varName = asmgen.asmVariableName(variable)
|
||||
when (dt) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_b""")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_w""")
|
||||
}
|
||||
DataType.STR -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #${numElements-1}
|
||||
jsr prog8_lib.func_reverse_b""")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr floats.func_reverse_f""")
|
||||
}
|
||||
in SplitWordArrayTypes -> {
|
||||
// reverse the lsb and msb arrays both, independently
|
||||
asmgen.out("""
|
||||
lda #<${varName}_lsb
|
||||
ldy #>${varName}_lsb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_b
|
||||
lda #<${varName}_msb
|
||||
ldy #>${varName}_msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.func_reverse_b""")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSort(fcall: PtBuiltinFunctionCall) {
|
||||
val variable = fcall.args.single() as PtIdentifier
|
||||
val symbol = asmgen.symbolTable.lookup(variable.name)
|
||||
val varName = asmgen.asmVariableName(variable)
|
||||
val (dt, numElements) = when(symbol) {
|
||||
is StStaticVariable -> symbol.dt to symbol.length!!
|
||||
is StMemVar -> symbol.dt to symbol.length!!
|
||||
else -> DataType.UNDEFINED to 0
|
||||
}
|
||||
when (dt) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements""")
|
||||
asmgen.out(if (dt == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements""")
|
||||
asmgen.out(if (dt == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
|
||||
}
|
||||
DataType.STR -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #${numElements-1}
|
||||
jsr prog8_lib.func_sort_ub""")
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
|
||||
in SplitWordArrayTypes -> TODO("split words sort")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcRor2(fcall: PtBuiltinFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
when (what.type) {
|
||||
@@ -837,63 +727,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true, true)
|
||||
}
|
||||
|
||||
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
val dt = fcall.args.single().type
|
||||
val array = fcall.args[0] as PtIdentifier
|
||||
when (dt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
|
||||
outputAddressAndLengthOfArray(array)
|
||||
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
outputAddressAndLengthOfArray(array)
|
||||
asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
outputAddressAndLengthOfArray(array)
|
||||
asmgen.out(" jsr floats.func_${fcall.name}_f_into_A")
|
||||
}
|
||||
in SplitWordArrayTypes -> {
|
||||
val numElements = (asmgen.symbolTable.lookup(array.name) as StStaticVariable).length
|
||||
when(fcall.name) {
|
||||
"any" -> {
|
||||
// any(lsb-array) or any(msb-array)
|
||||
val arrayName = asmgen.asmVariableName(array)
|
||||
asmgen.out("""
|
||||
lda #<${arrayName}_lsb
|
||||
ldy #>${arrayName}_lsb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
""")
|
||||
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
|
||||
asmgen.out(" bne +") // shortcircuit
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda #<${arrayName}_msb
|
||||
ldy #>${arrayName}_msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
""")
|
||||
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
pla
|
||||
ora P8ZP_SCRATCH_REG
|
||||
+""")
|
||||
}
|
||||
"all" -> {
|
||||
TODO("split words all")
|
||||
}
|
||||
else -> throw AssemblyError("weird call")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes, true)
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
val dt = fcall.args.single().type
|
||||
|
||||
@@ -82,7 +82,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
is PtAddressOf -> false
|
||||
is PtIdentifier -> false
|
||||
is PtIrRegister -> false
|
||||
is PtMemoryByte -> return usesOtherRegistersWhileEvaluating(arg.address)
|
||||
is PtMemoryByte -> true // TODO might not actually need extra registers if the value has to end up in A
|
||||
is PtNumber -> false
|
||||
is PtBool -> false
|
||||
else -> true
|
||||
@@ -110,9 +110,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
|
||||
}
|
||||
else {
|
||||
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
|
||||
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, usedA())
|
||||
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, usedA())
|
||||
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, usedA())
|
||||
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
|
||||
@@ -83,9 +83,80 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun fallbackTranslateForSimpleCondition(ifElse: PtIfElse) {
|
||||
val bittest = ifElse.condition as? PtBuiltinFunctionCall
|
||||
val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump
|
||||
|
||||
if(bittest!=null && bittest.name.startsWith("prog8_ifelse_bittest_")) {
|
||||
val variable = bittest.args[0] as PtIdentifier
|
||||
val bitnumber = (bittest.args[1] as PtNumber).number.toInt()
|
||||
val testForBitSet = bittest.name.endsWith("_set")
|
||||
when (bitnumber) {
|
||||
7 -> {
|
||||
// test via bit + N flag
|
||||
asmgen.out(" bit ${variable.name}")
|
||||
if(testForBitSet) {
|
||||
if(jumpAfterIf!=null) {
|
||||
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
|
||||
if(indirect)
|
||||
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
|
||||
if(ifElse.hasElse())
|
||||
throw AssemblyError("didn't expect else part here ${ifElse.position}")
|
||||
else
|
||||
asmgen.out(" bmi $asmLabel")
|
||||
}
|
||||
else
|
||||
translateIfElseBodies("bpl", ifElse)
|
||||
} else {
|
||||
if(jumpAfterIf!=null) {
|
||||
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
|
||||
if(indirect)
|
||||
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
|
||||
if(ifElse.hasElse())
|
||||
throw AssemblyError("didn't expect else part here ${ifElse.position}")
|
||||
else
|
||||
asmgen.out(" bpl $asmLabel")
|
||||
}
|
||||
else
|
||||
translateIfElseBodies("bmi", ifElse)
|
||||
}
|
||||
return
|
||||
}
|
||||
6 -> {
|
||||
// test via bit + V flag
|
||||
asmgen.out(" bit ${variable.name}")
|
||||
if(testForBitSet) {
|
||||
if(jumpAfterIf!=null) {
|
||||
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
|
||||
if(indirect)
|
||||
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
|
||||
if(ifElse.hasElse())
|
||||
throw AssemblyError("didn't expect else part here ${ifElse.position}")
|
||||
else
|
||||
asmgen.out(" bvs $asmLabel")
|
||||
}
|
||||
else
|
||||
translateIfElseBodies("bvc", ifElse)
|
||||
} else {
|
||||
if(jumpAfterIf!=null) {
|
||||
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
|
||||
if(indirect)
|
||||
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
|
||||
if(ifElse.hasElse())
|
||||
throw AssemblyError("didn't expect else part here ${ifElse.position}")
|
||||
else
|
||||
asmgen.out(" bvc $asmLabel")
|
||||
}
|
||||
else
|
||||
translateIfElseBodies("bvs", ifElse)
|
||||
}
|
||||
return
|
||||
}
|
||||
else -> throw AssemblyError("prog8_ifelse_bittest can only work on bits 7 and 6")
|
||||
}
|
||||
}
|
||||
|
||||
// the condition is "simple" enough to just assign its 0/1 value to a register and branch on that
|
||||
assignConditionValueToRegisterAndTest(ifElse.condition)
|
||||
val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump
|
||||
if(jumpAfterIf!=null)
|
||||
translateJumpElseBodies("bne", "beq", jumpAfterIf, ifElse.elseScope)
|
||||
else
|
||||
@@ -405,14 +476,14 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
val condition = stmt.condition as PtBinaryExpression
|
||||
if(signed) {
|
||||
// X>Y --> Y<X
|
||||
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
|
||||
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, true)
|
||||
cmpAwithByteValue(condition.left, true)
|
||||
if (jumpAfterIf != null)
|
||||
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
|
||||
else
|
||||
translateIfElseBodies("bpl", stmt)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
|
||||
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, false)
|
||||
cmpAwithByteValue(condition.right, false)
|
||||
if(jumpAfterIf!=null) {
|
||||
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
|
||||
@@ -848,10 +919,10 @@ _jump jmp ($asmLabel)
|
||||
}
|
||||
} else {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
|
||||
if(value.splitWords) {
|
||||
return compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
|
||||
return if(value.splitWords) {
|
||||
compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
|
||||
} else {
|
||||
return compareLsbMsb("$varname,y", "$varname+1,y")
|
||||
compareLsbMsb("$varname,y", "$varname+1,y")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -985,10 +1056,10 @@ _jump jmp ($asmLabel)
|
||||
}
|
||||
} else {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
|
||||
if(value.splitWords) {
|
||||
return compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
|
||||
return if(value.splitWords) {
|
||||
compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
|
||||
} else {
|
||||
return compareLsbMsb("$varname,y", "$varname+1,y")
|
||||
compareLsbMsb("$varname,y", "$varname+1,y")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,12 +284,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
SourceStorageKind.MEMORY -> {
|
||||
fun assignViaExprEval(expression: PtExpression) {
|
||||
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, true)
|
||||
}
|
||||
|
||||
val value = assign.source.memory!!
|
||||
when (value.address) {
|
||||
is PtNumber -> {
|
||||
@@ -304,10 +298,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, false)) {
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, true)
|
||||
} else {
|
||||
assignViaExprEval(value.address)
|
||||
assignByteFromAddressExpression(value.address, assign.target)
|
||||
}
|
||||
}
|
||||
else -> assignViaExprEval(value.address)
|
||||
else -> assignByteFromAddressExpression(value.address, assign.target)
|
||||
}
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
@@ -319,6 +313,106 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignByteFromAddressExpression(address: PtExpression, target: AsmAssignTarget) {
|
||||
if(address is PtBinaryExpression) {
|
||||
if(address.operator=="+" && address.right.type==DataType.UWORD) {
|
||||
if (address.left is PtIdentifier) {
|
||||
// use (zp),Y instead of explicitly calculating the full zp pointer value
|
||||
val pointer = (address.left as PtIdentifier).name
|
||||
when(val index=address.right) {
|
||||
is PtIdentifier -> {
|
||||
val indexName = index.name
|
||||
asmgen.out("""
|
||||
lda $pointer
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda $pointer+1
|
||||
clc
|
||||
adc $indexName+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldy $indexName
|
||||
lda (P8ZP_SCRATCH_W2),y""")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
}
|
||||
is PtNumber -> {
|
||||
val indexValue = index.number.toString()
|
||||
asmgen.out("""
|
||||
lda $pointer
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda $pointer+1
|
||||
clc
|
||||
adc #>$indexValue
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldy #<$indexValue
|
||||
lda (P8ZP_SCRATCH_W2),y""")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// else if(address.operator=="-") {
|
||||
// // does this ever occur? we could optimize it too, but it seems like a pathological case
|
||||
// }
|
||||
}
|
||||
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
}
|
||||
|
||||
private fun storeByteInAToAddressExpression(address: PtExpression, saveA: Boolean) {
|
||||
if(address is PtBinaryExpression) {
|
||||
if(address.operator=="+") {
|
||||
if (address.left is PtIdentifier && address.right.type==DataType.UWORD) {
|
||||
// use (zp),Y instead of explicitly calculating the full zp pointer value
|
||||
val pointer = (address.left as PtIdentifier).name
|
||||
when(val index=address.right) {
|
||||
is PtIdentifier -> {
|
||||
val indexName = index.name
|
||||
asmgen.out("""
|
||||
tax
|
||||
lda $pointer
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda $pointer+1
|
||||
clc
|
||||
adc $indexName+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldy $indexName
|
||||
txa
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
return
|
||||
}
|
||||
is PtNumber -> {
|
||||
val indexValue = index.number.toString()
|
||||
asmgen.out("""
|
||||
tax
|
||||
lda $pointer
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda $pointer+1
|
||||
clc
|
||||
adc #>$indexValue
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldy #<$indexValue
|
||||
txa
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// else if(address.operator=="-") {
|
||||
// // does this ever occur? we could optimize it too, but it seems like a pathological case
|
||||
// }
|
||||
}
|
||||
if(saveA) asmgen.out(" pha")
|
||||
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
if(saveA) asmgen.out(" pla")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
}
|
||||
|
||||
|
||||
private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) {
|
||||
when(val value = assign.source.expression!!) {
|
||||
is PtAddressOf -> {
|
||||
@@ -703,8 +797,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
RegisterOrPair.Y -> "y"
|
||||
else -> TODO("comparison to word register")
|
||||
}
|
||||
val assignTrue = PtInlineAssembly(" ld${reg} #1", false, assign.target.position)
|
||||
val assignFalse = PtInlineAssembly(" ld${reg} #0", false, assign.target.position)
|
||||
val assignTrue = PtInlineAssembly("\tld${reg} #1", false, assign.target.position)
|
||||
val assignFalse = PtInlineAssembly("\tld${reg} #0", false, assign.target.position)
|
||||
ifPart.add(assignTrue)
|
||||
elsePart.add(assignFalse)
|
||||
val ifelse = PtIfElse(assign.position)
|
||||
@@ -754,7 +848,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
sta ${assign.target.asmVarname}+1""", false, assign.target.position)
|
||||
}
|
||||
} else {
|
||||
assignTrue = PtInlineAssembly(" lda #1\n sta ${assign.target.asmVarname}", false, assign.target.position)
|
||||
assignTrue = PtInlineAssembly("\tlda #1\n sta ${assign.target.asmVarname}", false, assign.target.position)
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
@@ -839,6 +933,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun optimizedDivideExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
|
||||
// replacing division by shifting is done in an optimizer step.
|
||||
when(expr.type) {
|
||||
DataType.UBYTE -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
@@ -1010,7 +1105,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
} else {
|
||||
if (signed && shifts > 0) {
|
||||
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
|
||||
if(shifts==1)
|
||||
asmgen.out(" cmp #$80 | ror a")
|
||||
else
|
||||
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
|
||||
} else {
|
||||
repeat(shifts) {
|
||||
asmgen.out(" lsr a")
|
||||
@@ -1044,7 +1142,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
} else {
|
||||
if(signed && shifts>0) {
|
||||
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
|
||||
if(shifts==1) {
|
||||
asmgen.out("""
|
||||
pha
|
||||
tya
|
||||
cmp #$80
|
||||
ror a
|
||||
tay
|
||||
pla
|
||||
ror a""")
|
||||
}
|
||||
else
|
||||
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
|
||||
} else {
|
||||
if(shifts>0) {
|
||||
asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||
@@ -1108,10 +1217,17 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
is PtNumber -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out(" clc | adc #${right.number.toHex()}")
|
||||
else
|
||||
asmgen.out(" sec | sbc #${right.number.toHex()}")
|
||||
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if (expr.operator == "+")
|
||||
asmgen.out(" inc a")
|
||||
else
|
||||
asmgen.out(" dec a")
|
||||
} else {
|
||||
if (expr.operator == "+")
|
||||
asmgen.out(" clc | adc #${right.number.toHex()}")
|
||||
else
|
||||
asmgen.out(" sec | sbc #${right.number.toHex()}")
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
|
||||
return true
|
||||
}
|
||||
@@ -1246,8 +1362,39 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
is PtNumber -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
inc a
|
||||
bne +
|
||||
iny
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
dec a
|
||||
cmp #255
|
||||
bne +
|
||||
dey
|
||||
+""")
|
||||
}
|
||||
} else if(dt!=DataType.WORD && right.number.toInt() in 0..255) {
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #${right.number.toHex()}
|
||||
bcc +
|
||||
iny
|
||||
+""") } else if(expr.operator=="-") {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #${right.number.toHex()}
|
||||
bcs +
|
||||
dey
|
||||
+""")
|
||||
}
|
||||
} else {
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<${right.number.toHex()}
|
||||
tax
|
||||
@@ -1255,8 +1402,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
adc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
} else if(expr.operator=="-") {
|
||||
asmgen.out("""
|
||||
} else if(expr.operator=="-") {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<${right.number.toHex()}
|
||||
tax
|
||||
@@ -1264,6 +1411,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
sbc #>${right.number.toHex()}
|
||||
tay
|
||||
txa""")
|
||||
}
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
@@ -3143,11 +3291,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
else {
|
||||
if (regs !in Cx16VirtualRegisters) {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | phx")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | phy")
|
||||
RegisterOrPair.XY -> asmgen.out(" phx | phy")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
} else {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
}
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
@@ -3186,11 +3343,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
else {
|
||||
if (regs !in Cx16VirtualRegisters) {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | phx")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | phy")
|
||||
RegisterOrPair.XY -> asmgen.out(" phx | phy")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
} else {
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
}
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
@@ -3689,17 +3855,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
|
||||
fun storeViaExprEval() {
|
||||
when(addressExpr) {
|
||||
is PtNumber, is PtIdentifier -> {
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
}
|
||||
else -> {
|
||||
// same as above but we need to save the A register
|
||||
asmgen.out(" pha")
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.out(" pla")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
}
|
||||
is PtNumber, is PtIdentifier -> storeByteInAToAddressExpression(addressExpr, false)
|
||||
else -> storeByteInAToAddressExpression(addressExpr, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+55
-27
@@ -1086,6 +1086,21 @@ $shortcutLabel:""")
|
||||
}
|
||||
}
|
||||
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if(operator=="&" && value is PtPrefix && value.operator=="~") {
|
||||
// M &= ~A --> use TRB 65c02 instruction for that
|
||||
asmgen.assignExpressionToRegister(value.value, RegisterOrPair.A, dt in SignedDatatypes)
|
||||
asmgen.out(" trb $name")
|
||||
return
|
||||
}
|
||||
else if(operator=="|") {
|
||||
// M |= A --> use TSB 65c02 instruction for that
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
|
||||
asmgen.out(" tsb $name")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// normal evaluation
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
|
||||
inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt in SignedDatatypes)
|
||||
@@ -1094,6 +1109,15 @@ $shortcutLabel:""")
|
||||
|
||||
private fun inplacemodificationByteVariableWithVariable(name: String, dt: DataType, operator: String, otherName: String) {
|
||||
// note: no logical and/or shortcut here, not worth it due to simple right operand
|
||||
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if(operator=="|") {
|
||||
// M |= A --> use TSB 65c02 instruction for that
|
||||
asmgen.out(" lda $otherName | tsb $name")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
asmgen.out(" lda $name")
|
||||
inplacemodificationRegisterAwithVariable(operator, otherName, dt in SignedDatatypes)
|
||||
asmgen.out(" sta $name")
|
||||
@@ -1459,6 +1483,7 @@ $shortcutLabel:""")
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name")
|
||||
}
|
||||
"/" -> {
|
||||
// replacing division by shifting is done in an optimizer step.
|
||||
if (dt == DataType.UBYTE)
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name")
|
||||
else
|
||||
@@ -1642,7 +1667,7 @@ $shortcutLabel:""")
|
||||
}
|
||||
|
||||
private fun immediateOrInplace(name: String, value: Int) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02) && ((value and (value-1))==0)) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
asmgen.out(" lda #$value | tsb $name") // set bit
|
||||
} else {
|
||||
asmgen.out(" lda $name | ora #$value | sta $name")
|
||||
@@ -1807,33 +1832,36 @@ $shortcutLabel:""")
|
||||
}
|
||||
}
|
||||
"/" -> {
|
||||
if(value==0)
|
||||
// replacing division by shifting is done in an optimizer step.
|
||||
if(value==0) {
|
||||
throw AssemblyError("division by zero")
|
||||
if(dt==DataType.WORD) {
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
ldy $msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_w_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
}
|
||||
else {
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
ldy $msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_uw_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
} else {
|
||||
if(dt==DataType.WORD) {
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
ldy $msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_w_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
}
|
||||
else {
|
||||
asmgen.out("""
|
||||
lda $lsb
|
||||
ldy $msb
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.divmod_uw_asm
|
||||
sta $lsb
|
||||
sty $msb
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
"%" -> {
|
||||
|
||||
@@ -29,7 +29,7 @@ dependencies {
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
|
||||
|
||||
@@ -1390,9 +1390,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
val valueReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, vmDt, reg1=valueReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
|
||||
when(comparisonOperator) {
|
||||
"==" -> it += IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"!=" -> it += IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
it += when(comparisonOperator) {
|
||||
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"<" -> return null // TODO("array <0 inplace")) // TODO?
|
||||
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
|
||||
">" -> return null // TODO("array >0 inplace")) // TODO?
|
||||
@@ -1413,9 +1413,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADX, vmDt, reg1=valueReg, reg2=indexTr.resultReg, labelSymbol = array.variable.name)
|
||||
when(comparisonOperator) {
|
||||
"==" -> it += IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"!=" -> it += IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
it += when(comparisonOperator) {
|
||||
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
|
||||
"<" -> return null // TODO("array <0 inplace")) // TODO?
|
||||
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
|
||||
">" -> return null // TODO("array >0 inplace")) // TODO?
|
||||
|
||||
@@ -9,8 +9,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
return when(call.name) {
|
||||
"any" -> funcAny(call)
|
||||
"all" -> funcAll(call)
|
||||
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
|
||||
"cmp" -> funcCmp(call)
|
||||
"sgn" -> funcSgn(call)
|
||||
@@ -34,14 +32,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
|
||||
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
|
||||
"sort" -> funcSort(call)
|
||||
"reverse" -> funcReverse(call)
|
||||
"setlsb" -> funcSetLsbMsb(call, false)
|
||||
"setmsb" -> funcSetLsbMsb(call, true)
|
||||
"rol" -> funcRolRor(call)
|
||||
"ror" -> funcRolRor(call)
|
||||
"rol2" -> funcRolRor(call)
|
||||
"ror2" -> funcRolRor(call)
|
||||
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
|
||||
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(call)
|
||||
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
|
||||
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
|
||||
@@ -132,10 +130,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
|
||||
if(call.void)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
return if(call.void)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
else
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
|
||||
ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
|
||||
}
|
||||
|
||||
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
@@ -204,79 +202,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcAny(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
|
||||
if(arrayName.type in SplitWordArrayTypes) {
|
||||
// any(lsb-array) or any(msb-array)
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
|
||||
addToResult(result, trLsb, trLsb.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trLsb.resultReg)
|
||||
val shortcircuitLabel = codeGen.createLabelName()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = trLsb.resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcircuitLabel)
|
||||
it += IRInstruction(Opcode.PREPARECALL, immediate = 2)
|
||||
}
|
||||
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
|
||||
addToResult(result, trMsb, trMsb.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trMsb.resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=trLsb.resultReg, reg2=trMsb.resultReg), null)
|
||||
result += IRCodeChunk(shortcircuitLabel, null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, trLsb.resultReg, -1)
|
||||
}
|
||||
|
||||
val syscall =
|
||||
when (arrayName.type) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> IMSyscall.ANY_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
DataType.ARRAY_W -> IMSyscall.ANY_WORD
|
||||
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(arrayName)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcAll(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
|
||||
if(arrayName.type in SplitWordArrayTypes) {
|
||||
// this is a bit complicated to calculate.... have to check all recombined (lsb,msb) words for $0000
|
||||
TODO("all(split words $arrayName)")
|
||||
}
|
||||
|
||||
val syscall =
|
||||
when(arrayName.type) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> IMSyscall.ALL_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
DataType.ARRAY_W -> IMSyscall.ALL_WORD
|
||||
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(arrayName)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcAbs(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val sourceDt = call.args.single().type
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
@@ -369,65 +294,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
if(arrayName.type in SplitWordArrayTypes) {
|
||||
// reverse the lsb and msb arrays both, independently
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
|
||||
addToResult(result, trLsb, trLsb.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
|
||||
addToResult(result, trMsb, trMsb.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
val syscall =
|
||||
when(arrayName.type) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
|
||||
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
|
||||
else -> throw IllegalArgumentException("weird type to reverse")
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(arrayName)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcSort(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
|
||||
val syscall =
|
||||
when(arrayName.type) {
|
||||
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
|
||||
DataType.ARRAY_B -> IMSyscall.SORT_BYTE
|
||||
DataType.ARRAY_UW -> IMSyscall.SORT_UWORD
|
||||
DataType.ARRAY_W -> IMSyscall.SORT_WORD
|
||||
DataType.STR -> IMSyscall.SORT_UBYTE
|
||||
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
||||
in SplitWordArrayTypes -> TODO("split word sort")
|
||||
else -> throw IllegalArgumentException("weird type to sort")
|
||||
}
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
|
||||
val tr = exprGen.translateExpression(arrayName)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StNode
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StSub
|
||||
import prog8.code.ast.*
|
||||
@@ -451,7 +452,36 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
|
||||
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
|
||||
val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)
|
||||
|
||||
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw")) {
|
||||
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
|
||||
return translateStackFunctions(fcall, callTarget)
|
||||
}
|
||||
when(callTarget.scopedName) {
|
||||
"sys.clear_carry" -> {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(chunk, IRInstruction(Opcode.CLC), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
"sys.set_carry" -> {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(chunk, IRInstruction(Opcode.SEC), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
"sys.clear_irqd" -> {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(chunk, IRInstruction(Opcode.CLI), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
"sys.set_irqd" -> {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(chunk, IRInstruction(Opcode.SEI), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
}
|
||||
|
||||
when (callTarget) {
|
||||
is StSub -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
|
||||
@@ -590,6 +620,39 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateStackFunctions(fcall: PtFunctionCall, callTarget: StNode): ExpressionCodeResult {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
when(callTarget.scopedName) {
|
||||
"sys.push" -> {
|
||||
// push byte
|
||||
val tr = translateExpression(fcall.args.single())
|
||||
chunk += tr.chunks
|
||||
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
"sys.pushw" -> {
|
||||
// push word
|
||||
val tr = translateExpression(fcall.args.single())
|
||||
chunk += tr.chunks
|
||||
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.WORD, -1, -1)
|
||||
}
|
||||
"sys.pop" -> {
|
||||
// pop byte
|
||||
val popReg = codeGen.registers.nextFree()
|
||||
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=popReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, popReg, -1)
|
||||
}
|
||||
"sys.popw" -> {
|
||||
// pop word
|
||||
val popReg = codeGen.registers.nextFree()
|
||||
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=popReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.WORD, popReg, -1)
|
||||
}
|
||||
else -> throw AssemblyError("unknown stack subroutine called")
|
||||
}
|
||||
}
|
||||
|
||||
private fun callRomSubWithMultipleReturnValues(
|
||||
callTarget: StRomSub,
|
||||
fcall: PtFunctionCall,
|
||||
|
||||
@@ -5,7 +5,6 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.intermediate.*
|
||||
import kotlin.io.path.readBytes
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
class IRCodeGen(
|
||||
@@ -391,10 +390,10 @@ class IRCodeGen(
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
when(iterable) {
|
||||
is PtRange -> {
|
||||
if(iterable.from is PtNumber && iterable.to is PtNumber)
|
||||
result += translateForInConstantRange(forLoop, loopvar)
|
||||
result += if(iterable.from is PtNumber && iterable.to is PtNumber)
|
||||
translateForInConstantRange(forLoop, loopvar)
|
||||
else
|
||||
result += translateForInNonConstantRange(forLoop, loopvar)
|
||||
translateForInNonConstantRange(forLoop, loopvar)
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
require(forLoop.variable.name == loopvar.scopedName)
|
||||
@@ -682,13 +681,11 @@ class IRCodeGen(
|
||||
return code
|
||||
}
|
||||
|
||||
internal val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() }
|
||||
|
||||
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
val pow2 = powersOfTwoInt.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
|
||||
@@ -712,7 +709,7 @@ class IRCodeGen(
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
val pow2 = powersOfTwoInt.indexOf(factor)
|
||||
if(pow2==1) {
|
||||
// just shift 1 bit
|
||||
code += if(knownAddress!=null)
|
||||
@@ -785,19 +782,32 @@ class IRCodeGen(
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1 && !signed) {
|
||||
code += IRInstruction(Opcode.LSR, dt, reg1=reg) // simple single bit shift
|
||||
}
|
||||
else if(pow2>=1 &&!signed) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
code += if(signed)
|
||||
IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
|
||||
else
|
||||
IRInstruction(Opcode.LSRN, dt, reg1=reg, reg2=pow2reg)
|
||||
val pow2 = powersOfTwoInt.indexOf(factor)
|
||||
if(pow2>=0) {
|
||||
if(signed) {
|
||||
if(pow2==1) {
|
||||
// simple single bit shift (signed)
|
||||
code += IRInstruction(Opcode.ASR, dt, reg1=reg)
|
||||
} else {
|
||||
// just shift multiple bits (signed)
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
code += IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
|
||||
}
|
||||
} else {
|
||||
if(pow2==1) {
|
||||
// simple single bit shift (unsigned)
|
||||
code += IRInstruction(Opcode.LSR, dt, reg1=reg)
|
||||
} else {
|
||||
// just shift multiple bits (unsigned)
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
|
||||
code += IRInstruction(Opcode.LSRN, dt, reg1 = reg, reg2 = pow2reg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
} else {
|
||||
// regular div
|
||||
code += if (factor == 0) {
|
||||
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff)
|
||||
} else {
|
||||
@@ -806,39 +816,56 @@ class IRCodeGen(
|
||||
else
|
||||
IRInstruction(Opcode.DIV, dt, reg1=reg, immediate = factor)
|
||||
}
|
||||
return code
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
internal fun divideByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int, signed: Boolean): IRCodeChunk {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(factor==1)
|
||||
return code
|
||||
val pow2 = powersOfTwo.indexOf(factor)
|
||||
if(pow2==1 && !signed) {
|
||||
// just simple bit shift
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRM, dt, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol)
|
||||
val pow2 = powersOfTwoInt.indexOf(factor)
|
||||
if(pow2>=0) {
|
||||
// can do bit shift instead of division
|
||||
if(signed) {
|
||||
if(pow2==1) {
|
||||
// just simple bit shift (signed)
|
||||
code += if (knownAddress != null)
|
||||
IRInstruction(Opcode.ASRM, dt, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.ASRM, dt, labelSymbol = symbol)
|
||||
} else {
|
||||
// just shift multiple bits (signed)
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
|
||||
code += if (knownAddress != null)
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
||||
}
|
||||
} else {
|
||||
if(pow2==1) {
|
||||
// just simple bit shift (unsigned)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRM, dt, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol)
|
||||
}
|
||||
else {
|
||||
// just shift multiple bits (unsigned)
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
else if(pow2>=1 && !signed) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
code += if(signed) {
|
||||
if(knownAddress!=null)
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
||||
}
|
||||
else {
|
||||
if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress)
|
||||
else
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
||||
}
|
||||
} else {
|
||||
else
|
||||
{
|
||||
// regular div
|
||||
if (factor == 0) {
|
||||
val reg = registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff)
|
||||
@@ -863,8 +890,8 @@ class IRCodeGen(
|
||||
IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(ifElse: PtIfElse): IRCodeChunks {
|
||||
@@ -1291,6 +1318,10 @@ class IRCodeGen(
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode) {
|
||||
|
||||
if(condition is PtBuiltinFunctionCall && condition.name.startsWith("prog8_ifelse_bittest_"))
|
||||
throw AssemblyError("IR codegen doesn't have special instructions for dedicated BIT tests and should just still use normal AND")
|
||||
|
||||
val tr = expressionEval.translateExpression(condition)
|
||||
result += tr.chunks
|
||||
if(ifElse.hasElse()) {
|
||||
@@ -1503,17 +1534,16 @@ class IRCodeGen(
|
||||
private fun translate(jump: PtJump): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
if(jump.address!=null) {
|
||||
chunk += IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
|
||||
chunk += if(jump.address!=null) {
|
||||
IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
|
||||
} else {
|
||||
if (jump.identifier != null) {
|
||||
if(isIndirectJump(jump)) {
|
||||
chunk += IRInstruction(Opcode.JUMPI, labelSymbol = jump.identifier!!.name)
|
||||
IRInstruction(Opcode.JUMPI, labelSymbol = jump.identifier!!.name)
|
||||
} else {
|
||||
chunk += IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
|
||||
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
|
||||
}
|
||||
}
|
||||
else
|
||||
} else
|
||||
throw AssemblyError("weird jump")
|
||||
}
|
||||
result += chunk
|
||||
|
||||
@@ -263,6 +263,24 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ins.opcode== Opcode.SEI || ins.opcode== Opcode.CLI) {
|
||||
if(idx < chunk.instructions.size-1) {
|
||||
val insAfter = chunk.instructions[idx+1]
|
||||
if(insAfter.opcode == ins.opcode) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
else if(ins.opcode== Opcode.SEI && insAfter.opcode== Opcode.CLI) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
else if(ins.opcode== Opcode.CLI && insAfter.opcode== Opcode.SEI) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
@@ -80,21 +80,52 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
instr[1].opcode shouldBe Opcode.INC
|
||||
}
|
||||
|
||||
test("remove double sec/clc") {
|
||||
test("remove double sec/clc/sei/cli") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.CLC)
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 6
|
||||
irProg.chunks().single().instructions.size shouldBe 12
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 1
|
||||
instr.size shouldBe 2
|
||||
instr[0].opcode shouldBe Opcode.CLC
|
||||
instr[1].opcode shouldBe Opcode.CLI
|
||||
}
|
||||
|
||||
test("remove double sec/clc/sei/cli reversed") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.CLC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.SEC),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.CLI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
IRInstruction(Opcode.SEI),
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 12
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 2
|
||||
instr[0].opcode shouldBe Opcode.SEC
|
||||
instr[1].opcode shouldBe Opcode.SEI
|
||||
}
|
||||
|
||||
test("push followed by pop") {
|
||||
|
||||
@@ -91,7 +91,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
program.encoding.encodeString(leftString.value, leftString.encoding) + program.encoding.encodeString(rightString.value, rightString.encoding),
|
||||
leftString.encoding)
|
||||
}
|
||||
val concatStr = StringLiteral(concatenated, leftString.encoding, expr.position)
|
||||
val concatStr = StringLiteral.create(concatenated, leftString.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, concatStr, parent))
|
||||
}
|
||||
else if(expr.operator=="*" && rightconst!=null && expr.left is StringLiteral) {
|
||||
@@ -99,7 +99,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
val part = expr.left as StringLiteral
|
||||
if(part.value.isEmpty())
|
||||
errors.warn("resulting string has length zero", part.position)
|
||||
val newStr = StringLiteral(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
|
||||
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,11 +154,10 @@ class VarConstantValueTypeAdjuster(
|
||||
if(func==listOf("clamp")) {
|
||||
val t1 = functionCallExpr.args[0].inferType(program)
|
||||
if(t1.isKnown) {
|
||||
val replaceFunc: String
|
||||
if(t1.isBytes) {
|
||||
replaceFunc = if(t1.istype(DataType.BYTE)) "clamp__byte" else "clamp__ubyte"
|
||||
val replaceFunc = if(t1.isBytes) {
|
||||
if(t1.istype(DataType.BYTE)) "clamp__byte" else "clamp__ubyte"
|
||||
} else if(t1.isInteger) {
|
||||
replaceFunc = if(t1.istype(DataType.WORD)) "clamp__word" else "clamp__uword"
|
||||
if(t1.istype(DataType.WORD)) "clamp__word" else "clamp__uword"
|
||||
} else {
|
||||
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
|
||||
return noModifications
|
||||
|
||||
@@ -14,9 +14,6 @@ import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
|
||||
class ExpressionSimplifier(private val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
|
||||
@@ -104,7 +101,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if (!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
||||
return noModifications
|
||||
|
||||
// X + (-A) --> X - A
|
||||
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
|
||||
@@ -686,7 +683,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteral(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
|
||||
} else if (cv in powersOfTwo) {
|
||||
} else if (cv in powersOfTwoFloat) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
return null
|
||||
@@ -711,6 +708,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
return null
|
||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||
when (cv) {
|
||||
0.0 -> return null // fall through to regular float division to properly deal with division by zero
|
||||
-1.0 -> {
|
||||
// '/' -> -left
|
||||
if (expr.operator == "/") {
|
||||
@@ -738,12 +736,10 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
in powersOfTwo -> {
|
||||
if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) {
|
||||
// Unsigned number divided by a power of two => shift right
|
||||
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values),
|
||||
// so we leave that as is and let the code generator deal with it.
|
||||
val numshifts = log2(cv).toInt()
|
||||
in powersOfTwoFloat -> {
|
||||
val numshifts = powersOfTwoFloat.indexOf(cv)
|
||||
if (leftDt in IntegerDatatypes) {
|
||||
// division by a power of two => shift right (signed and unsigned)
|
||||
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
@@ -795,14 +791,14 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
// left
|
||||
return expr2.left
|
||||
}
|
||||
in powersOfTwo -> {
|
||||
in powersOfTwoFloat -> {
|
||||
if (leftValue.inferType(program).isInteger) {
|
||||
// times a power of two => shift left
|
||||
val numshifts = log2(cv).toInt()
|
||||
return BinaryExpression(expr2.left, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
in negativePowersOfTwo -> {
|
||||
in negativePowersOfTwoFloat -> {
|
||||
if (leftValue.inferType(program).isInteger) {
|
||||
// times a negative power of two => negate, then shift
|
||||
val numshifts = log2(-cv).toInt()
|
||||
|
||||
@@ -30,11 +30,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine}
|
||||
if(!containsSubsOrVariables) {
|
||||
if(subroutine.statements.size==1 || (subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))) {
|
||||
if(subroutine !== program.entrypoint) {
|
||||
if (!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine }
|
||||
if (!containsSubsOrVariables) {
|
||||
if (subroutine.statements.size == 1 || (subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))) {
|
||||
if (subroutine !== program.entrypoint) {
|
||||
// subroutine is possible candidate to be inlined
|
||||
subroutine.inline =
|
||||
when (val stmt = subroutine.statements[0]) {
|
||||
@@ -87,9 +87,9 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
} else
|
||||
false
|
||||
targetInline || valueInline
|
||||
} else if(stmt.target.identifier!=null && stmt.isAugmentable) {
|
||||
} else if (stmt.target.identifier != null && stmt.isAugmentable) {
|
||||
val binExpr = stmt.value as BinaryExpression
|
||||
if(binExpr.operator in "+-" && binExpr.right.constValue(program)?.number==1.0) {
|
||||
if (binExpr.operator in "+-" && binExpr.right.constValue(program)?.number == 1.0) {
|
||||
makeFullyScoped(stmt.target.identifier!!)
|
||||
makeFullyScoped(binExpr.left as IdentifierReference)
|
||||
true
|
||||
@@ -121,8 +121,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine.inline && subroutine.statements.size>1) {
|
||||
require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))
|
||||
if (subroutine.inline && subroutine.statements.size > 1) {
|
||||
require(subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))
|
||||
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
|
||||
}
|
||||
}
|
||||
@@ -147,6 +147,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: FunctionCallStatement) {
|
||||
makeFullyScoped(call.target)
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
@@ -169,6 +170,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: FunctionCallExpression) {
|
||||
makeFullyScoped(call.target)
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
|
||||
@@ -21,7 +21,8 @@ class StatementOptimizer(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource.size==1) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in functions.purefunctionNames) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
if("ignore_unused" !in parent.definingBlock.options())
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,8 @@ class UnusedCodeRemover(private val program: Program,
|
||||
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
|
||||
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
|
||||
if(declIndex==singleUseIndex-1) {
|
||||
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
|
||||
if("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
|
||||
val fcall = assignment.value as IFunctionCall
|
||||
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
|
||||
return listOf(
|
||||
|
||||
@@ -2,7 +2,8 @@ plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
// id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
id 'io.github.goooler.shadow' version '8.1.7'
|
||||
id 'com.peterabeles.gversion' version '1.10.2'
|
||||
}
|
||||
|
||||
@@ -31,7 +32,7 @@ dependencies {
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation project(':virtualmachine')
|
||||
implementation "org.antlr:antlr4-runtime:4.13.1"
|
||||
implementation "org.antlr:antlr4-runtime:4.13.2"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
|
||||
@@ -39,7 +40,7 @@ dependencies {
|
||||
|
||||
testImplementation project(':codeCore')
|
||||
testImplementation project(':intermediate')
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
; any() and all() checks on arrays/memory buffers.
|
||||
; These were builtin functions in older versions of the language.
|
||||
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
anyall {
|
||||
sub any(uword arrayptr, uword num_elements) -> bool {
|
||||
; -- returns true if any byte in the array is not zero.
|
||||
cx16.r1 = arrayptr
|
||||
if msb(num_elements)==0 {
|
||||
for cx16.r0L in 0 to lsb(num_elements)-1 {
|
||||
if cx16.r1[cx16.r0L]!=0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
repeat num_elements {
|
||||
if @(cx16.r1)!=0
|
||||
return true
|
||||
cx16.r1++
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub all(uword arrayptr, uword num_elements) -> bool {
|
||||
; -- returns true if all bytes in the array are not zero.
|
||||
cx16.r1 = arrayptr
|
||||
if msb(num_elements)==0 {
|
||||
for cx16.r0L in 0 to lsb(num_elements)-1 {
|
||||
if cx16.r1[cx16.r0L]==0
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
repeat num_elements {
|
||||
if @(cx16.r1)==0
|
||||
return false
|
||||
cx16.r1++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub anyw(uword arrayptr, uword num_elements) -> bool {
|
||||
; -- returns true if any word in the array is not zero.
|
||||
; doesn't work on @split arrays.
|
||||
cx16.r1 = arrayptr
|
||||
if msb(num_elements)==0 {
|
||||
repeat lsb(num_elements) {
|
||||
if peekw(cx16.r1)!=0
|
||||
return true
|
||||
cx16.r1+=2
|
||||
}
|
||||
return false
|
||||
}
|
||||
repeat num_elements {
|
||||
if peekw(cx16.r1)!=0
|
||||
return true
|
||||
cx16.r1+=2
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub allw(uword arrayptr, uword num_elements) -> bool {
|
||||
; -- returns true if all words in the array are not zero.
|
||||
; doesn't work on @split arrays.
|
||||
cx16.r1 = arrayptr
|
||||
if msb(num_elements)==0 {
|
||||
repeat lsb(num_elements) {
|
||||
if peekw(cx16.r1)==0
|
||||
return false
|
||||
cx16.r1+=2
|
||||
}
|
||||
return true
|
||||
}
|
||||
repeat num_elements {
|
||||
if peekw(cx16.r1)==0
|
||||
return false
|
||||
cx16.r1+=2
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
; experimental buffer data structures
|
||||
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
smallringbuffer {
|
||||
; -- A ringbuffer (FIFO queue) that occupies a single page in memory, containing 255 bytes maximum.
|
||||
; You can store and retrieve words too.
|
||||
; It's optimized for speed and depends on the byte-wrap-around feature when doing incs and decs.
|
||||
|
||||
ubyte fill
|
||||
ubyte head
|
||||
ubyte tail
|
||||
ubyte[256] buffer
|
||||
|
||||
sub init() {
|
||||
; -- (re)initialize the ringbuffer, you must call this before using the other routines
|
||||
head = fill = 0
|
||||
tail = 255
|
||||
}
|
||||
|
||||
sub put(ubyte value) -> bool {
|
||||
; -- store a byte in the buffer, returns success
|
||||
if fill==255
|
||||
return false
|
||||
buffer[head] = value
|
||||
head++
|
||||
fill++
|
||||
return true
|
||||
}
|
||||
|
||||
sub putw(uword value) -> bool {
|
||||
; -- store a word in the buffer, returns success
|
||||
if fill>=254
|
||||
return false
|
||||
fill += 2
|
||||
buffer[head] = lsb(value)
|
||||
head++
|
||||
buffer[head] = msb(value)
|
||||
head++
|
||||
return true
|
||||
}
|
||||
|
||||
sub get() -> ubyte {
|
||||
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
|
||||
if fill==0 {
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
}
|
||||
fill--
|
||||
tail++
|
||||
sys.set_carry()
|
||||
return buffer[tail]
|
||||
}
|
||||
|
||||
sub getw() -> uword {
|
||||
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
|
||||
if fill<2 {
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
}
|
||||
fill -= 2
|
||||
tail++
|
||||
cx16.r0L = buffer[tail]
|
||||
tail++
|
||||
cx16.r0H = buffer[tail]
|
||||
sys.set_carry()
|
||||
return cx16.r0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ringbuffer {
|
||||
; -- A ringbuffer (FIFO queue) that occupies a single page in memory, containing 8 KB maximum.
|
||||
; You can store and retrieve words too.
|
||||
|
||||
uword fill
|
||||
uword head
|
||||
uword tail
|
||||
uword buffer_ptr = memory("ringbuffer", 8192, 0)
|
||||
|
||||
sub init() {
|
||||
; -- (re)initialize the ringbuffer, you must call this before using the other routines
|
||||
head = fill = 0
|
||||
tail = 8191
|
||||
}
|
||||
|
||||
sub put(ubyte value) -> bool {
|
||||
; -- store a byte in the buffer, returns success
|
||||
if fill==8192
|
||||
return false
|
||||
buffer_ptr[head] = value
|
||||
inc_head()
|
||||
fill++
|
||||
return true
|
||||
}
|
||||
|
||||
sub putw(uword value) -> bool {
|
||||
; -- store a word in the buffer, returns success
|
||||
if fill>=8191
|
||||
return false
|
||||
fill += 2
|
||||
buffer_ptr[head] = lsb(value)
|
||||
inc_head()
|
||||
buffer_ptr[head] = msb(value)
|
||||
inc_head()
|
||||
return true
|
||||
}
|
||||
|
||||
sub get() -> ubyte {
|
||||
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
|
||||
if fill==0 {
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
}
|
||||
fill--
|
||||
inc_tail()
|
||||
cx16.r0L = buffer_ptr[tail]
|
||||
sys.set_carry()
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
sub getw() -> uword {
|
||||
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
|
||||
if fill<2 {
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
}
|
||||
fill -= 2
|
||||
inc_tail()
|
||||
cx16.r0L = buffer_ptr[tail]
|
||||
inc_tail()
|
||||
cx16.r0H = buffer_ptr[tail]
|
||||
sys.set_carry()
|
||||
return cx16.r0
|
||||
}
|
||||
|
||||
sub inc_head() {
|
||||
head++
|
||||
if msb(head)==$20
|
||||
head=0
|
||||
}
|
||||
|
||||
sub inc_tail() {
|
||||
tail++
|
||||
if msb(tail)==$20
|
||||
tail=0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; TODO ringbuffer (FIFO queue) should use banked ram on the X16, but still work on virtual
|
||||
; TODO stack (LIFO queue) using more than 1 page of ram (maybe even banked ram on the x16)
|
||||
@@ -6,108 +6,6 @@ func_sign_f_into_A .proc
|
||||
jmp SIGN
|
||||
.pend
|
||||
|
||||
func_swap_f .proc
|
||||
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||
ldy #4
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_reverse_f .proc
|
||||
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
_left_index = P8ZP_SCRATCH_W2
|
||||
_right_index = P8ZP_SCRATCH_W2+1
|
||||
_loop_count = P8ZP_SCRATCH_REG
|
||||
pha
|
||||
jsr a_times_5
|
||||
sec
|
||||
sbc #5
|
||||
sta _right_index
|
||||
lda #0
|
||||
sta _left_index
|
||||
pla
|
||||
lsr a
|
||||
sta _loop_count
|
||||
_loop ; push the left indexed float on the stack
|
||||
ldy _left_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
; copy right index float to left index float
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
inc _right_index
|
||||
ldy _right_index
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy _left_index
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
; pop the float off the stack into the right index float
|
||||
ldy _right_index
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
inc _left_index
|
||||
lda _right_index
|
||||
sec
|
||||
sbc #9
|
||||
sta _right_index
|
||||
dec _loop_count
|
||||
bne _loop
|
||||
rts
|
||||
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
a_times_5 .proc
|
||||
sta P8ZP_SCRATCH_B1
|
||||
@@ -118,26 +16,6 @@ a_times_5 .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_any_f_into_A .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_any_b_into_A
|
||||
.pend
|
||||
|
||||
func_all_f_into_A .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_into_A
|
||||
.pend
|
||||
|
||||
func_any_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_any_b_stack
|
||||
.pend
|
||||
|
||||
func_all_f_stack .proc
|
||||
jsr a_times_5
|
||||
jmp prog8_lib.func_all_b_stack
|
||||
.pend
|
||||
|
||||
func_abs_f_into_FAC1 .proc
|
||||
jsr MOVFM
|
||||
jmp ABS
|
||||
|
||||
@@ -517,16 +517,16 @@ _stop
|
||||
|
||||
asmsub internal_ubyte2decimal(ubyte value @A) -> ubyte @Y, ubyte @X, ubyte @A {
|
||||
%asm {{
|
||||
ldy #'0'-1
|
||||
ldx #'9'+1
|
||||
ldy #'0'-1
|
||||
ldx #'9'+1
|
||||
sec
|
||||
- iny
|
||||
sbc #100
|
||||
bcs -
|
||||
sbc #100
|
||||
bcs -
|
||||
- dex
|
||||
adc #10
|
||||
bmi -
|
||||
adc #'0'-1
|
||||
adc #10
|
||||
bmi -
|
||||
adc #'0'-1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -379,6 +379,7 @@ 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.
|
||||
; It is assumed the file size is less than 64 K.
|
||||
; 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
|
||||
@@ -393,12 +394,12 @@ m_in_buffer sta $ffff
|
||||
return total_read
|
||||
}
|
||||
|
||||
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y {
|
||||
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
|
||||
; Routine to read text lines from a text file. Lines must be less than 255 characters.
|
||||
; Reads characters from the input file UNTIL a newline or return character (or EOF).
|
||||
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
|
||||
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
|
||||
; I/O error status should be checked by the caller itself via READST() routine.
|
||||
; The I/O error status byte is returned in A.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -415,7 +416,8 @@ _loop jsr cbm.CHRIN
|
||||
_line_end dey ; get rid of the trailing end-of-line char
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
_end rts
|
||||
_end jsr cbm.READST
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -467,6 +469,7 @@ _end rts
|
||||
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||
; -- write the given number of bytes to the currently open file
|
||||
; you can call this multiple times to append more data
|
||||
if num_bytes!=0 {
|
||||
reset_write_channel()
|
||||
do {
|
||||
@@ -863,6 +866,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
|
||||
; Note: this will not work if you have already read the last byte of the file! Then you must close and reopen the file first.
|
||||
ubyte[6] command = ['p',0,0,0,0,0]
|
||||
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
|
||||
command[2] = lsb(pos_loword)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
; 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!
|
||||
;
|
||||
; NOTE: the bitmap screen data is positioned in vram at $0:0000
|
||||
;
|
||||
; SCREEN MODE LIST:
|
||||
; mode 0 = reset back to default text mode
|
||||
@@ -254,6 +255,7 @@ gfx2 {
|
||||
;; color <<= gfx2.plot.shift4c[lsb(xx) & 3]
|
||||
cx16.r2L = lsb(xx) & 3
|
||||
when color & 3 {
|
||||
0 -> color = 0
|
||||
1 -> color = gfx2.plot.shiftedleft_4c_1[cx16.r2L]
|
||||
2 -> color = gfx2.plot.shiftedleft_4c_2[cx16.r2L]
|
||||
3 -> color = gfx2.plot.shiftedleft_4c_3[cx16.r2L]
|
||||
@@ -272,10 +274,10 @@ gfx2 {
|
||||
|
||||
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
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||
}
|
||||
|
||||
}
|
||||
@@ -547,6 +549,7 @@ gfx2 {
|
||||
; color &= 3
|
||||
; color <<= shift4c[cx16.r2L]
|
||||
when color & 3 {
|
||||
0 -> color = 0
|
||||
1 -> color = shiftedleft_4c_1[cx16.r2L]
|
||||
2 -> color = shiftedleft_4c_2[cx16.r2L]
|
||||
3 -> color = shiftedleft_4c_3[cx16.r2L]
|
||||
@@ -626,10 +629,18 @@ gfx2 {
|
||||
}
|
||||
|
||||
sub fill(uword x, uword y, ubyte new_color) {
|
||||
; reuse a few virtual registers in ZP for variables
|
||||
&ubyte fillm = &cx16.r7L
|
||||
&ubyte seedm = &cx16.r8L
|
||||
&ubyte cmask = &cx16.r8H
|
||||
&ubyte vub = &cx16.r13L
|
||||
&ubyte nvub = &cx16.r13H
|
||||
ubyte[4] amask = [$c0,$30,$0c,$03] ; array of cmask bytes
|
||||
|
||||
; 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
|
||||
const ubyte MAXDEPTH = 100
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
@@ -641,6 +652,7 @@ gfx2 {
|
||||
word x2
|
||||
byte dy
|
||||
cx16.r10L = new_color
|
||||
|
||||
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
|
||||
if cx16.r12L==MAXDEPTH
|
||||
return
|
||||
@@ -704,39 +716,27 @@ gfx2 {
|
||||
return
|
||||
if xx<0 or xx>width-1 or yy<0 or yy>height-1
|
||||
return
|
||||
if gfx2.active_mode == 2 set_color_masks()
|
||||
push_stack(xx, xx, yy, 1)
|
||||
push_stack(xx, xx, yy + 1, -1)
|
||||
word left = 0
|
||||
while cx16.r12L!=0 {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
; possible speed optimization: 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--
|
||||
when active_mode {
|
||||
1 -> if fill_scanline_left_8bpp() goto skip
|
||||
2 -> if fill_scanline_left_2bpp() goto skip
|
||||
}
|
||||
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.r9s = xx
|
||||
; possible speed optimization: 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++
|
||||
when active_mode {
|
||||
1 -> fill_scanline_right_8bpp()
|
||||
2 -> fill_scanline_right_2bpp()
|
||||
}
|
||||
if cx16.r9s!=xx
|
||||
horizontal_line(cx16.r9, yy as uword, xx-cx16.r9s as uword, cx16.r10L)
|
||||
|
||||
push_stack(left, xx - 1, yy, dy)
|
||||
if xx > x2 + 1
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
@@ -750,6 +750,132 @@ skip:
|
||||
left = xx
|
||||
} until xx>x2
|
||||
}
|
||||
|
||||
sub set_vera_address() {
|
||||
; set both data0 and data1 addresses (expects H in R1L, M/L in R0)
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.r1L
|
||||
cx16.VERA_ADDR = cx16.r0
|
||||
cx16.VERA_CTRL = 1
|
||||
cx16.VERA_ADDR_H = cx16.r1L
|
||||
cx16.VERA_ADDR = cx16.r0
|
||||
}
|
||||
|
||||
sub fill_scanline_left_8bpp() -> bool {
|
||||
void addr_mul_24_for_lores_256c(yy as uword, xx as uword) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r1L |= %00011000 ; auto decrement enabled
|
||||
set_vera_address()
|
||||
cx16.r9s = xx
|
||||
while xx >= 0 {
|
||||
if cx16.VERA_DATA0 != cx16.r11L
|
||||
break
|
||||
cx16.VERA_DATA1 = cx16.r10L
|
||||
xx--
|
||||
}
|
||||
return xx==cx16.r9s
|
||||
}
|
||||
|
||||
sub fill_scanline_right_8bpp() {
|
||||
void addr_mul_24_for_lores_256c(yy as uword, xx as uword) ; 24 bits result is in r0 and r1L (highest byte)
|
||||
cx16.r1L |= %00010000 ; auto increment enabled
|
||||
set_vera_address()
|
||||
while xx <= width-1 {
|
||||
if cx16.VERA_DATA0 != cx16.r11L
|
||||
break
|
||||
cx16.VERA_DATA1 = cx16.r10L
|
||||
xx++
|
||||
}
|
||||
}
|
||||
|
||||
sub fill_scanline_left_2bpp() -> bool {
|
||||
uword vx = xx as uword
|
||||
void gfx2.addr_mul_24_for_highres_4c(yy as uword,vx)
|
||||
cx16.r1L |= %0001_1000 ; auto decrement
|
||||
set_vera_address()
|
||||
cmask = amask[lsb(vx) & 3] ; set the color mask for the first color pel
|
||||
|
||||
repeat {
|
||||
vub = cx16.VERA_DATA0 ; read the VERA color data for 4 pixels
|
||||
if cmask == $03 { ; only speed fill from far right
|
||||
; speed fill
|
||||
if vub == seedm { ; all four colors match the seed
|
||||
nvub = fillm ; replace all four colors at once
|
||||
xx -= 4
|
||||
goto set_byte ; go on
|
||||
}
|
||||
}
|
||||
|
||||
; replace one color at a time
|
||||
nvub = vub
|
||||
while cmask != 0 {
|
||||
if vub & cmask == seedm & cmask {
|
||||
nvub &= ~cmask
|
||||
nvub |= cmask & fillm
|
||||
; %asm{{
|
||||
; lda p8v_cmask
|
||||
; trb p8v_nvub
|
||||
; and p8v_fillm
|
||||
; tsb p8v_nvub
|
||||
; }}
|
||||
xx--
|
||||
cmask <<= 2
|
||||
} else { ; not the seed color, finish here
|
||||
cx16.VERA_DATA1 = nvub
|
||||
return vx == xx
|
||||
}
|
||||
}
|
||||
set_byte:
|
||||
cx16.VERA_DATA1 = nvub
|
||||
if xx <= 0 break
|
||||
cmask = $03
|
||||
}
|
||||
return vx == xx
|
||||
}
|
||||
|
||||
sub fill_scanline_right_2bpp() {
|
||||
void gfx2.addr_mul_24_for_highres_4c(yy as uword,xx as uword)
|
||||
cx16.r1L |= %00010000 ; auto increment
|
||||
set_vera_address()
|
||||
cmask = amask[lsb(xx) & 3] ; set the color mask for the first color pel
|
||||
|
||||
repeat {
|
||||
vub = cx16.VERA_DATA0 ; read the VERA color data for 4 pixels
|
||||
; speed fill
|
||||
if vub == seedm { ; all four colors match the seed
|
||||
nvub = fillm ; replace all four colors at once
|
||||
xx += 4
|
||||
goto set_byte ; go on
|
||||
}
|
||||
; replace one color at a time
|
||||
nvub = vub
|
||||
while cmask != 0 {
|
||||
if vub & cmask == seedm & cmask {
|
||||
nvub &= ~cmask
|
||||
nvub |= cmask & fillm
|
||||
; %asm{{
|
||||
; lda p8v_cmask
|
||||
; trb p8v_nvub
|
||||
; and p8v_fillm
|
||||
; tsb p8v_nvub
|
||||
; }}
|
||||
xx++
|
||||
cmask >>= 2
|
||||
} else { ; not the seed color finish here
|
||||
cx16.VERA_DATA1 = nvub
|
||||
return
|
||||
}
|
||||
}
|
||||
set_byte:
|
||||
cx16.VERA_DATA1 = nvub
|
||||
if xx >= gfx2.width-1 break
|
||||
cmask = $C0
|
||||
}
|
||||
}
|
||||
|
||||
sub set_color_masks() {
|
||||
seedm = cx16.r11L | (cx16.r11L<<2) | (cx16.r11L<<4) | (cx16.r11L<<6) ; seed mask
|
||||
fillm = cx16.r10L | (cx16.r10L<<2) | (cx16.r10L<<4) | (cx16.r10L<<6) ; fill mask
|
||||
}
|
||||
}
|
||||
|
||||
sub position(uword @zp xx, uword yy) {
|
||||
|
||||
@@ -358,10 +358,10 @@ drawmode: ora cx16.r15L
|
||||
|
||||
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
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||
}
|
||||
|
||||
}
|
||||
@@ -708,7 +708,7 @@ invert:
|
||||
; 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
|
||||
const ubyte MAXDEPTH = 100
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
@@ -957,15 +957,15 @@ cdraw_mod2 ora cx16.VERA_DATA1
|
||||
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)
|
||||
cx16.VERA_CTRL = 0
|
||||
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)
|
||||
cx16.VERA_CTRL = 0
|
||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,15 @@ sprites {
|
||||
cx16.vpoke_mask(1, sprite_reg+1, %11110000, msb(addr)) ; address 16:13
|
||||
}
|
||||
|
||||
sub get_data_ptr(ubyte spritenum) {
|
||||
inline asmsub get_data_ptr(ubyte spritenum @A) -> ubyte @R1, uword @R0 {
|
||||
; -- returns the VRAM address where the sprite's bitmap data is stored
|
||||
; R1 (byte) = the vera bank (0 or 1), R0 (word) = the address.
|
||||
%asm {{
|
||||
jsr p8b_sprites.p8s_get_data_ptr_internal
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_data_ptr_internal(ubyte spritenum) {
|
||||
sprite_reg = VERA_SPRITEREGS + spritenum*$0008
|
||||
cx16.r0L = cx16.vpeek(1, sprite_reg)
|
||||
cx16.r0H = cx16.vpeek(1, sprite_reg+1)
|
||||
@@ -176,7 +182,7 @@ sprites {
|
||||
}
|
||||
|
||||
sub set_mousepointer_image(uword data, bool compressed) {
|
||||
get_data_ptr(0) ; the mouse cursor is sprite 0
|
||||
get_data_ptr_internal(0) ; the mouse cursor is sprite 0
|
||||
if cx16.r1L==0 and cx16.r0==0
|
||||
return ; mouse cursor not enabled
|
||||
ubyte vbank = cx16.r1L
|
||||
|
||||
@@ -6,6 +6,39 @@
|
||||
cbm {
|
||||
; Commodore (CBM) common variables, vectors and kernal routines
|
||||
|
||||
; irq, system and hardware vectors (common across cbm machines):
|
||||
&uword IERROR = $0300
|
||||
&uword IMAIN = $0302
|
||||
&uword ICRNCH = $0304
|
||||
&uword IQPLOP = $0306
|
||||
&uword IGONE = $0308
|
||||
&uword IEVAL = $030a
|
||||
&ubyte SAREG = $030c ; register storage for A for SYS calls
|
||||
&ubyte SXREG = $030d ; register storage for X for SYS calls
|
||||
&ubyte SYREG = $030e ; register storage for Y for SYS calls
|
||||
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
|
||||
&uword USRADD = $0311 ; vector for the USR() basic command
|
||||
; $0313 is unused.
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword IOPEN = $031a
|
||||
&uword ICLOSE = $031c
|
||||
&uword ICHKIN = $031e
|
||||
&uword ICKOUT = $0320
|
||||
&uword ICLRCH = $0322
|
||||
&uword IBASIN = $0324
|
||||
&uword IBSOUT = $0326
|
||||
&uword ISTOP = $0328
|
||||
&uword IGETIN = $032a
|
||||
&uword ICLALL = $032c
|
||||
; $032e has a X16 specific function (KEYHDL) so you'll find this as cx16.KEYHDL
|
||||
&uword ILOAD = $0330
|
||||
&uword ISAVE = $0332
|
||||
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
|
||||
&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
|
||||
|
||||
|
||||
; STROUT --> use txt.print
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
@@ -117,42 +150,14 @@ asmsub kbdbuf_clear() {
|
||||
|
||||
cx16 {
|
||||
|
||||
; irq, system and hardware vectors:
|
||||
&uword IERROR = $0300
|
||||
&uword IMAIN = $0302
|
||||
&uword ICRNCH = $0304
|
||||
&uword IQPLOP = $0306
|
||||
&uword IGONE = $0308
|
||||
&uword IEVAL = $030a
|
||||
&ubyte SAREG = $030c ; register storage for A for SYS calls
|
||||
&ubyte SXREG = $030d ; register storage for X for SYS calls
|
||||
&ubyte SYREG = $030e ; register storage for Y for SYS calls
|
||||
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
|
||||
&uword USRADD = $0311 ; vector for the USR() basic command
|
||||
; $0313 is unused.
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword IOPEN = $031a
|
||||
&uword ICLOSE = $031c
|
||||
&uword ICHKIN = $031e
|
||||
&uword ICKOUT = $0320
|
||||
&uword ICLRCH = $0322
|
||||
&uword IBASIN = $0324
|
||||
&uword IBSOUT = $0326
|
||||
&uword ISTOP = $0328
|
||||
&uword IGETIN = $032a
|
||||
&uword ICLALL = $032c
|
||||
; cx16 specific vectors and variables
|
||||
&uword KEYHDL = $032e ; keyboard scan code handler see examples/cx16/keyboardhandler.p8
|
||||
&uword ILOAD = $0330
|
||||
&uword ISAVE = $0332
|
||||
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
|
||||
&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 ; (ram bank 0): for intercepting BASIN/CHRIN key strokes. See set_chrin_keyhandler()
|
||||
&ubyte edkeybk = $ac05 ; ...the RAM bank of the handler routine, if not in low ram
|
||||
|
||||
&ubyte stavec = $03b2 ; argument for stash()
|
||||
|
||||
|
||||
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
|
||||
&uword r0 = $0002
|
||||
@@ -395,8 +400,8 @@ romsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) -> ubyte @A, ubyte @X, ubyte @Y, bool @Pc ; also see SCREEN or get_screen_mode()
|
||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y)
|
||||
romsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff74 = fetch(ubyte zp_startaddr @A, ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X) ; note: The the zero page address containing the base address is passed in stavec ($03B2)
|
||||
romsub $ff7d = PRIMM()
|
||||
|
||||
; high level graphics & fonts
|
||||
@@ -1070,8 +1075,8 @@ asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
|
||||
trb cx16.VERA_IEN ; disable all IRQ sources
|
||||
+ lda #<_irq_dispatcher
|
||||
ldy #>_irq_dispatcher
|
||||
sta cx16.CINV
|
||||
sty cx16.CINV+1
|
||||
sta cbm.CINV
|
||||
sty cbm.CINV+1
|
||||
plp
|
||||
rts
|
||||
|
||||
@@ -1195,6 +1200,7 @@ asmsub set_sprcol_irq_handler(uword address @AY) clobbers(A) {
|
||||
asmsub set_aflow_irq_handler(uword address @AY) clobbers(A) {
|
||||
; Sets the AFLOW irq handler to use with enable_irq_handlers(). Also enables AFLOW irqs.
|
||||
; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes.
|
||||
; NOTE: the handler itself must fill the audio fifo buffer to at least 25% full again (1 KB) or the aflow irq will keep triggering!
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
@@ -1322,6 +1328,23 @@ _continue iny
|
||||
void cx16.i2c_write_byte($42, $05, cx16.r0L)
|
||||
}
|
||||
|
||||
asmsub rom_version() clobbers(Y) -> ubyte @A, bool @Pc {
|
||||
; Returns the KERNEL ROM version. Carry set if pre-release, clear if offical release.
|
||||
%asm{{
|
||||
; the ROM BANK is unknown on entry
|
||||
ldy $01
|
||||
stz $01 ; KERNEL ROM
|
||||
clc ; prepare for released ROM
|
||||
lda $FF80
|
||||
bpl _final ; pre-release versions are negative
|
||||
eor #$FF ; twos complement
|
||||
ina
|
||||
sec
|
||||
_final:
|
||||
sty $01
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sys {
|
||||
@@ -1378,9 +1401,9 @@ asmsub init_system() {
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
sei
|
||||
lda cx16.CINV
|
||||
lda cbm.CINV
|
||||
sta restore_irq._orig_irqvec
|
||||
lda cx16.CINV+1
|
||||
lda cbm.CINV+1
|
||||
sta restore_irq._orig_irqvec+1
|
||||
lda #PROG8_VARSHIGH_RAMBANK
|
||||
sta $00 ; select ram bank
|
||||
@@ -1418,9 +1441,9 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #<_irq_handler
|
||||
sta cx16.CINV
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta cx16.CINV+1
|
||||
sta cbm.CINV+1
|
||||
lda #1
|
||||
tsb cx16.VERA_IEN ; enable the vsync irq
|
||||
cli
|
||||
@@ -1449,9 +1472,9 @@ asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda _orig_irqvec
|
||||
sta cx16.CINV
|
||||
sta cbm.CINV
|
||||
lda _orig_irqvec+1
|
||||
sta cx16.CINV+1
|
||||
sta cbm.CINV+1
|
||||
lda cx16.VERA_IEN
|
||||
and #%11110000 ; disable all Vera IRQs but the vsync
|
||||
ora #%00000001
|
||||
@@ -1478,9 +1501,9 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
ldy cx16.r0+1
|
||||
jsr set_rasterline
|
||||
lda #<_raster_irq_handler
|
||||
sta cx16.CINV
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cx16.CINV+1
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
rts
|
||||
|
||||
|
||||
@@ -262,6 +262,12 @@ sub iso16() {
|
||||
cx16.screen_set_charset(10, 0) ; charset
|
||||
}
|
||||
|
||||
sub kata() {
|
||||
; -- switch to katakana character set (requires rom 48+)
|
||||
cbm.CHROUT($0f) ; iso mode
|
||||
cx16.screen_set_charset(12, 0) ; charset
|
||||
}
|
||||
|
||||
asmsub scroll_left() clobbers(A, X, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
; Somewhat experimental Vera FX support.
|
||||
; Docs:
|
||||
; https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2010%20-%20VERA%20FX%20Reference.md
|
||||
; https://github.com/X16Community/x16-docs/blob/fb63156cca2d6de98be0577aacbe4ddef458f896/X16%20Reference%20-%2010%20-%20VERA%20FX%20Reference.md
|
||||
; https://docs.google.com/document/d/1q34uWOiM3Be2pnaHRVgSdHySI-qsiQWPTo_gfE54PTg
|
||||
|
||||
verafx {
|
||||
@@ -166,6 +166,8 @@ verafx {
|
||||
}
|
||||
|
||||
sub transparency(bool enable) {
|
||||
; Set transparent write mode for VeraFX cached writes and also for normal writes to DATA0/DATA.
|
||||
; If enabled, pixels with value 0 do not modify VRAM when written (so they are "transparent")
|
||||
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
|
||||
if enable
|
||||
cx16.VERA_FX_CTRL |= %10000000
|
||||
|
||||
@@ -333,6 +333,7 @@ m_in_buffer sta $ffff
|
||||
|
||||
sub f_read_all(uword bufferpointer) -> uword {
|
||||
; -- read the full contents of the file, returns number of bytes read.
|
||||
; It is assumed the file size is less than 64 K.
|
||||
if not iteration_in_progress
|
||||
return 0
|
||||
|
||||
@@ -346,12 +347,13 @@ m_in_buffer sta $ffff
|
||||
return total_read
|
||||
}
|
||||
|
||||
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y {
|
||||
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
|
||||
; Routine to read text lines from a text file. Lines must be less than 255 characters.
|
||||
; Reads characters from the input file UNTIL a newline or return character (or EOF).
|
||||
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
|
||||
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
|
||||
; I/O error status should be checked by the caller itself via READST() routine.
|
||||
; The I/O error status byte is returned in A.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -368,7 +370,8 @@ _loop jsr cbm.CHRIN
|
||||
_line_end dey ; get rid of the trailing end-of-line char
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
_end rts
|
||||
_end jsr cbm.READST
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -405,6 +408,7 @@ _end rts
|
||||
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||
; -- write the given number of bytes to the currently open file
|
||||
; you can call this multiple times to append more data
|
||||
if num_bytes!=0 {
|
||||
reset_write_channel()
|
||||
repeat num_bytes {
|
||||
|
||||
@@ -1,60 +1,5 @@
|
||||
; ---- builtin functions
|
||||
|
||||
|
||||
func_any_b_into_A .proc
|
||||
; -- any(array), array in P8ZP_SCRATCH_W1, num bytes in A
|
||||
sta _cmp_mod+1 ; self-modifying code
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
bne _got_any
|
||||
iny
|
||||
_cmp_mod cpy #255 ; modified
|
||||
bne -
|
||||
lda #0
|
||||
rts
|
||||
_got_any lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_all_b_into_A .proc
|
||||
; -- all(array), array in P8ZP_SCRATCH_W1, num bytes in A
|
||||
sta _cmp_mod+1 ; self-modifying code
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq _got_not_all
|
||||
iny
|
||||
_cmp_mod cpy #255 ; modified
|
||||
bne -
|
||||
lda #1
|
||||
_got_not_all rts
|
||||
.pend
|
||||
|
||||
func_any_w_into_A .proc
|
||||
asl a
|
||||
jmp func_any_b_into_A
|
||||
.pend
|
||||
|
||||
func_all_w_into_A .proc
|
||||
; -- all(warray), array in P8ZP_SCRATCH_W1, num bytes in A
|
||||
asl a ; times 2 because of word
|
||||
sta _cmp_mod+1 ; self-modifying code
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
bne +
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
bne ++
|
||||
lda #0
|
||||
rts
|
||||
+ iny
|
||||
+ iny
|
||||
_cmp_mod cpy #255 ; modified
|
||||
bne -
|
||||
lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
abs_b_into_A .proc
|
||||
; -- A = abs(A)
|
||||
cmp #0
|
||||
|
||||
@@ -248,31 +248,25 @@ _arg_s2 .word 0
|
||||
|
||||
strcmp_mem .proc
|
||||
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
|
||||
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
|
||||
; Returns -1,0,1 in A, depending on the ordering. Clobbers Y.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
bne +
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
bne _return_minusone
|
||||
beq _return
|
||||
+ cmp (P8ZP_SCRATCH_W2),y
|
||||
bcc _return_minusone
|
||||
bne _return_one
|
||||
inc P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_return_one
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
beq _c1_zero
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
beq _equal
|
||||
bmi _less
|
||||
lda #1
|
||||
_return rts
|
||||
_return_minusone
|
||||
lda #-1
|
||||
rts
|
||||
_less lda #-1
|
||||
rts
|
||||
_equal iny
|
||||
bne _loop
|
||||
_c1_zero lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
lda #-1
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
@@ -96,7 +96,16 @@ sub atan(float value) -> float {
|
||||
; two-argument arctangent that returns an angle in the correct quadrant
|
||||
; for the signs of x and y, normalized to the range [0, 2π]
|
||||
sub atan2(float y, float x) -> float {
|
||||
float atn = atan(y / x)
|
||||
float atn
|
||||
if x == 0 {
|
||||
atn = π/2
|
||||
if y == 0 return 0
|
||||
if y < 0 {
|
||||
atn += π
|
||||
}
|
||||
} else {
|
||||
atn = atan(y / x)
|
||||
}
|
||||
if x < 0 atn += π
|
||||
if atn < 0 atn += 2*π
|
||||
return atn
|
||||
|
||||
@@ -151,6 +151,37 @@ _found tya
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub rfind(uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc {
|
||||
; Locates the first position of the given character in the string, starting from the right.
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_B1
|
||||
sta _str
|
||||
sty _str+1
|
||||
jsr string.length
|
||||
dey
|
||||
lda _str
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _str+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq _found
|
||||
dey
|
||||
cpy #255
|
||||
bne -
|
||||
_notfound lda #255
|
||||
clc
|
||||
rts
|
||||
_found tya
|
||||
sec
|
||||
rts
|
||||
|
||||
_str .word 0
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub contains(uword string @AY, ubyte character @X) -> bool @Pc {
|
||||
; Just return true/false if the character is in the given string or not.
|
||||
%asm {{
|
||||
@@ -199,9 +230,9 @@ _found tya
|
||||
|
||||
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.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
|
||||
@@ -11,7 +11,7 @@ diskio {
|
||||
%ir {{
|
||||
loadm.w r65534,diskio.load.filenameptr
|
||||
loadm.w r65535,diskio.load.address_override
|
||||
syscall 61 (): r0.b
|
||||
syscall 48 (): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
@@ -72,6 +72,7 @@ diskio {
|
||||
|
||||
sub f_read_all(uword bufferpointer) -> uword {
|
||||
; -- read the full contents of the file, returns number of bytes read.
|
||||
; It is assumed the file size is less than 64 K.
|
||||
txt.print("@TODO: f_read_all\n")
|
||||
return 0
|
||||
}
|
||||
@@ -81,7 +82,7 @@ diskio {
|
||||
; Reads characters from the input file UNTIL a newline or return character (or EOF).
|
||||
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
|
||||
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
|
||||
; I/O error status should be checked by the caller itself via READST() routine.
|
||||
; This routine is not able here to return the status as well in a secondary return value, so you have to do that yourself.
|
||||
txt.print("@TODO: f_readline\n")
|
||||
return 0
|
||||
}
|
||||
@@ -107,6 +108,7 @@ diskio {
|
||||
|
||||
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
|
||||
; -- write the given number of bytes to the currently open file
|
||||
; you can call this multiple times to append more data
|
||||
txt.print("@TODO: f_write\n")
|
||||
return false
|
||||
}
|
||||
@@ -157,7 +159,7 @@ diskio {
|
||||
loadm.w r65533,diskio.save.filenameptr
|
||||
loadm.w r65534,diskio.save.start_address
|
||||
loadm.w r65535,diskio.save.savesize
|
||||
syscall 58 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
|
||||
syscall 45 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
@@ -169,7 +171,7 @@ diskio {
|
||||
loadm.w r65533,diskio.save.filenameptr
|
||||
loadm.w r65534,diskio.save.start_address
|
||||
loadm.w r65535,diskio.save.savesize
|
||||
syscall 58 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
|
||||
syscall 45 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
@@ -184,7 +186,7 @@ diskio {
|
||||
%ir {{
|
||||
loadm.w r65534,diskio.load.filenameptr
|
||||
loadm.w r65535,diskio.load.address_override
|
||||
syscall 56 (r65534.w, r65535.w): r0.w
|
||||
syscall 43 (r65534.w, r65535.w): r0.w
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
@@ -196,7 +198,7 @@ diskio {
|
||||
%ir {{
|
||||
loadm.w r65534,diskio.load_raw.filenameptr
|
||||
loadm.w r65535,diskio.load_raw.start_address
|
||||
syscall 57 (r65534.w, r65535.w): r0.w
|
||||
syscall 44 (r65534.w, r65535.w): r0.w
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
@@ -205,7 +207,7 @@ diskio {
|
||||
; -- delete a file on the drive
|
||||
%ir {{
|
||||
loadm.w r65535,diskio.delete.filenameptr
|
||||
syscall 59 (r65535.w)
|
||||
syscall 46 (r65535.w)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -214,7 +216,7 @@ diskio {
|
||||
%ir {{
|
||||
loadm.w r65534,diskio.rename.oldfileptr
|
||||
loadm.w r65535,diskio.rename.newfileptr
|
||||
syscall 60 (r65534.w, r65535.w)
|
||||
syscall 47 (r65534.w, r65535.w)
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ sub print(float value) {
|
||||
; ---- prints the floating point value (without a newline and no leading spaces).
|
||||
%ir {{
|
||||
loadm.f fr65535,floats.print.value
|
||||
syscall 25 (fr65535.f)
|
||||
syscall 15 (fr65535.f)
|
||||
return
|
||||
}}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ sub tostr(float value) -> str {
|
||||
%ir {{
|
||||
load.w r65535,floats.tostr.buffer
|
||||
loadm.f fr65535,floats.tostr.value
|
||||
syscall 47 (r65535.w, fr65535.f)
|
||||
syscall 34 (r65535.w, fr65535.f)
|
||||
load.w r0,floats.tostr.buffer
|
||||
returnr.w r0
|
||||
}}
|
||||
@@ -34,7 +34,7 @@ sub parse(str value) -> float {
|
||||
; -- parse a string value of a number to float
|
||||
%ir {{
|
||||
loadm.w r65535,floats.parse.value
|
||||
syscall 45 (r65535.w): fr0.f
|
||||
syscall 32 (r65535.w): fr0.f
|
||||
returnr.f fr0
|
||||
}}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ sub ceil(float value) -> float {
|
||||
|
||||
sub rnd() -> float {
|
||||
%ir {{
|
||||
syscall 35 () : fr0.f
|
||||
syscall 22 () : fr0.f
|
||||
returnr.f fr0
|
||||
}}
|
||||
}
|
||||
@@ -155,7 +155,7 @@ sub rnd() -> float {
|
||||
sub rndseed(float seed) {
|
||||
%ir {{
|
||||
loadm.f fr65535,floats.rndseed.seed
|
||||
syscall 32 (fr65535.f)
|
||||
syscall 19 (fr65535.f)
|
||||
return
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -164,14 +164,14 @@ math {
|
||||
|
||||
sub rnd() -> ubyte {
|
||||
%ir {{
|
||||
syscall 33 (): r0.b
|
||||
syscall 20 (): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub rndw() -> uword {
|
||||
%ir {{
|
||||
syscall 34 (): r0.w
|
||||
syscall 21 (): r0.w
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
@@ -199,7 +199,7 @@ math {
|
||||
%ir {{
|
||||
loadm.w r65534,math.rndseed.seed1
|
||||
loadm.w r65535,math.rndseed.seed2
|
||||
syscall 31 (r65534.w, r65535.w)
|
||||
syscall 19 (r65534.w, r65535.w)
|
||||
return
|
||||
}}
|
||||
}
|
||||
@@ -280,7 +280,7 @@ math {
|
||||
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
|
||||
syscall 31 (r65532.b, r65533.b, r65534.b, r65535.b): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
@@ -294,7 +294,7 @@ math {
|
||||
; - 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
|
||||
syscall 33 (): r0.w
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ monogfx {
|
||||
; 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
|
||||
const ubyte MAXDEPTH = 100
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
@@ -423,11 +423,10 @@ monogfx {
|
||||
while xx >= 0 {
|
||||
if pget(xx as uword, yy as uword) as ubyte != cx16.r11L
|
||||
break
|
||||
plot(xx as uword, yy as uword, cx16.r10L as bool)
|
||||
xx--
|
||||
}
|
||||
if x1!=xx
|
||||
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L as bool)
|
||||
else
|
||||
if x1==xx
|
||||
goto skip
|
||||
|
||||
left = xx + 1
|
||||
@@ -436,15 +435,12 @@ monogfx {
|
||||
xx = x1 + 1
|
||||
|
||||
do {
|
||||
cx16.r9 = xx as uword
|
||||
while xx <= width-1 {
|
||||
if pget(xx as uword, yy as uword) as ubyte != cx16.r11L
|
||||
break
|
||||
plot(xx as uword, yy as uword, cx16.r10L as bool)
|
||||
xx++
|
||||
}
|
||||
if cx16.r9!=xx
|
||||
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L as bool)
|
||||
|
||||
push_stack(left, xx - 1, yy, dy)
|
||||
if xx > x2 + 1
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
|
||||
@@ -68,6 +68,21 @@ string {
|
||||
return 255
|
||||
}
|
||||
|
||||
sub rfind(uword stringptr, ubyte character) -> ubyte {
|
||||
; Locates the first position of the given character in the string, starting from the right.
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
|
||||
ubyte ix
|
||||
for ix in string.length(stringptr)-1 downto 0 {
|
||||
if stringptr[ix]==character {
|
||||
sys.set_carry()
|
||||
return ix
|
||||
}
|
||||
}
|
||||
sys.clear_carry()
|
||||
return 255
|
||||
}
|
||||
|
||||
sub contains(str st, ubyte character) -> bool {
|
||||
void find(st, character)
|
||||
if_cs
|
||||
@@ -83,7 +98,7 @@ string {
|
||||
%ir {{
|
||||
loadm.w r65534,string.copy.source
|
||||
loadm.w r65535,string.copy.target
|
||||
syscall 52 (r65534.w, r65535.w): r0.b
|
||||
syscall 39 (r65534.w, r65535.w): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
@@ -97,13 +112,13 @@ string {
|
||||
|
||||
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.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%ir {{
|
||||
loadm.w r65534,string.compare.st1
|
||||
loadm.w r65535,string.compare.st2
|
||||
syscall 29 (r65534.w, r65535.w) : r0.b
|
||||
syscall 16 (r65534.w, r65535.w) : r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ sys {
|
||||
%ir {{
|
||||
loadm.w r65534,sys.internal_stringcopy.source
|
||||
loadm.w r65535,sys.internal_stringcopy.tgt
|
||||
syscall 52 (r65534.w, r65535.w): r0.b
|
||||
syscall 39 (r65534.w, r65535.w): r0.b
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ sys {
|
||||
loadm.w r65533,sys.memcopy.source
|
||||
loadm.w r65534,sys.memcopy.tgt
|
||||
loadm.w r65535,sys.memcopy.count
|
||||
syscall 49 (r65533.w, r65534.w, r65535.w)
|
||||
syscall 36 (r65533.w, r65534.w, r65535.w)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ sys {
|
||||
loadm.w r65533,sys.memset.mem
|
||||
loadm.w r65534,sys.memset.numbytes
|
||||
loadm.b r65535,sys.memset.value
|
||||
syscall 50 (r65533.w, r65534.w, r65535.b)
|
||||
syscall 37 (r65533.w, r65534.w, r65535.b)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ sys {
|
||||
loadm.w r65533,sys.memsetw.mem
|
||||
loadm.w r65534,sys.memsetw.numwords
|
||||
loadm.w r65535,sys.memsetw.value
|
||||
syscall 51 (r65533.w, r65534.w, r65535.w)
|
||||
syscall 38 (r65533.w, r65534.w, r65535.w)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,18 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
sub set_irqd() {
|
||||
%ir {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
sub clear_irqd() {
|
||||
%ir {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
sub disable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
@@ -128,7 +140,7 @@ sys {
|
||||
%ir {{
|
||||
loadm.w r65534,sys.gfx_getpixel.xx
|
||||
loadm.w r65535,sys.gfx_getpixel.yy
|
||||
syscall 30 (r65534.w, r65535.w): r0.b
|
||||
syscall 17 (r65534.w, r65535.w): r0.b
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ txt {
|
||||
|
||||
sub width() -> ubyte {
|
||||
%ir {{
|
||||
syscall 62 (): r0.w
|
||||
syscall 49 (): r0.w
|
||||
returnr.b r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub height() -> ubyte {
|
||||
%ir {{
|
||||
syscall 62 (): r0.w
|
||||
syscall 49 (): r0.w
|
||||
msig.b r1,r0
|
||||
returnr.b r1
|
||||
}}
|
||||
|
||||
@@ -102,10 +102,14 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
if(bytes2float!=null)
|
||||
return convertBytesToFloat(bytes2float!!, compilationTarget!!)
|
||||
if(float2bytes!=null)
|
||||
return convertFloatToBytes(float2bytes!!, compilationTarget!!)
|
||||
if(bytes2float!=null) {
|
||||
convertBytesToFloat(bytes2float!!, compilationTarget!!)
|
||||
return true
|
||||
}
|
||||
if(float2bytes!=null) {
|
||||
convertFloatToBytes(float2bytes!!, compilationTarget!!)
|
||||
return true
|
||||
}
|
||||
|
||||
if(varsHighBank==0 && compilationTarget==Cx16Target.NAME) {
|
||||
System.err.println("On the Commander X16, HiRAM bank 0 is used by the kernal and can't be used.")
|
||||
@@ -139,7 +143,8 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
}
|
||||
|
||||
if(startVm==true) {
|
||||
return runVm(moduleFiles.first())
|
||||
runVm(moduleFiles.first())
|
||||
return true
|
||||
}
|
||||
|
||||
val processedSymbols = processSymbolDefs(symbolDefs) ?: return false
|
||||
@@ -297,21 +302,19 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun convertFloatToBytes(number: String, target: String): Boolean {
|
||||
fun convertFloatToBytes(number: String, target: String) {
|
||||
val tgt = getCompilationTargetByName(target)
|
||||
val dbl = number.toDouble()
|
||||
val bytes = tgt.machine.convertFloatToBytes(dbl)
|
||||
print("$dbl in bytes on '$target': ")
|
||||
println(bytes.joinToString(","))
|
||||
return true
|
||||
}
|
||||
|
||||
fun convertBytesToFloat(bytelist: String, target: String): Boolean {
|
||||
fun convertBytesToFloat(bytelist: String, target: String) {
|
||||
val tgt = getCompilationTargetByName(target)
|
||||
val bytes = bytelist.split(',').map { it.trim().toUByte() }
|
||||
val number = tgt.machine.convertBytesToFloat(bytes)
|
||||
println("floating point value on '$target': $number")
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
|
||||
@@ -329,9 +332,8 @@ private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
|
||||
return result
|
||||
}
|
||||
|
||||
fun runVm(irFilename: String): Boolean {
|
||||
fun runVm(irFilename: String) {
|
||||
val irFile = Path(irFilename)
|
||||
val vmdef = VirtualMachineDefinition()
|
||||
vmdef.launchEmulator(0, irFile)
|
||||
return true
|
||||
}
|
||||
@@ -19,8 +19,6 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
|
||||
"sqrt__ubyte" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
|
||||
"sqrt__uword" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
|
||||
"sqrt__float" to { a, p, prg -> oneFloatArgOutputFloat(a, p, prg) { sqrt(it) } },
|
||||
"any" to { a, p, prg -> collectionArgBoolResult(a, p, prg) { array->array.any { it!=0.0 } } },
|
||||
"all" to { a, p, prg -> collectionArgBoolResult(a, p, prg) { array->array.all { it!=0.0 } } },
|
||||
"lsb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 255).toDouble() } },
|
||||
"msb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 8 and 255).toDouble()} },
|
||||
"mkword" to ::builtinMkword,
|
||||
@@ -77,17 +75,6 @@ private fun oneFloatArgOutputFloat(args: List<Expression>, position: Position, p
|
||||
return NumericLiteral(DataType.FLOAT, function(constval.number), args[0].position)
|
||||
}
|
||||
|
||||
private fun collectionArgBoolResult(args: List<Expression>, position: Position, program: Program, function: (arg: List<Double>)->Boolean): NumericLiteral {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
||||
|
||||
val array= args[0] as? ArrayLiteral ?: throw NotConstArgumentException()
|
||||
val constElements = array.value.map{it.constValue(program)?.number}
|
||||
if(constElements.contains(null))
|
||||
throw NotConstArgumentException()
|
||||
return NumericLiteral.fromBoolean(function(constElements.mapNotNull { it }), args[0].position)
|
||||
}
|
||||
|
||||
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
// 1 arg, type = int, result type= uword
|
||||
if(args.size!=1)
|
||||
@@ -140,6 +127,8 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
return NumericLiteral.optimalInteger(arraySize, position)
|
||||
if(args[0] is ArrayLiteral)
|
||||
return NumericLiteral.optimalInteger((args[0] as ArrayLiteral).value.size, position)
|
||||
if(args[0] is StringLiteral)
|
||||
return NumericLiteral.optimalInteger((args[0] as StringLiteral).value.length, position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("len argument should be an identifier", position)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program)
|
||||
|
||||
@@ -302,10 +302,9 @@ fun parseMainModule(filepath: Path,
|
||||
.filter { it.isFromFilesystem }
|
||||
.map { Path(it.origin) }
|
||||
val compilerOptions = determineCompilationOptions(program, compTarget)
|
||||
// depending on the machine and compiler options we may have to include some libraries
|
||||
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
|
||||
importer.importImplicitLibraryModule(lib)
|
||||
|
||||
// import the default modules
|
||||
importer.importImplicitLibraryModule("syslib")
|
||||
if(compilerOptions.compTarget.name!=VMTarget.NAME && !compilerOptions.experimentalCodegen) {
|
||||
importer.importImplicitLibraryModule("math")
|
||||
}
|
||||
|
||||
@@ -1418,6 +1418,13 @@ internal class AstChecker(private val program: Program,
|
||||
if(target is VarDecl) {
|
||||
if(target.datatype !in IterableDatatypes && target.datatype!=DataType.UWORD)
|
||||
errors.err("indexing requires an iterable or address uword variable", arrayIndexedExpression.position)
|
||||
val indexVariable = arrayIndexedExpression.indexer.indexExpr as? IdentifierReference
|
||||
if(indexVariable!=null) {
|
||||
if(indexVariable.targetVarDecl(program)?.datatype in SignedDatatypes) {
|
||||
errors.err("variable array indexing can't be performed with signed variables", indexVariable.position)
|
||||
return
|
||||
}
|
||||
}
|
||||
val arraysize = target.arraysize?.constIndex()
|
||||
val index = arrayIndexedExpression.indexer.constIndex()
|
||||
if(arraysize!=null) {
|
||||
|
||||
@@ -177,7 +177,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
'_'
|
||||
}.joinToString("")
|
||||
val textEncoding = (call as Node).definingModule.textEncoding
|
||||
call.args[0] = StringLiteral(processed, textEncoding, name.position)
|
||||
call.args[0] = StringLiteral.create(processed, textEncoding, name.position)
|
||||
call.args[0].linkParents(call as Node)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -59,16 +59,15 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
|
||||
// integer case (only if both are the same type)
|
||||
val leftC = expr.left as? BinaryExpression
|
||||
val rightC = expr.right as? BinaryExpression
|
||||
if(leftC!=null && rightC!=null && leftC.operator=="==" && rightC.operator=="==") {
|
||||
if(expr.operator=="and" && leftC!=null && rightC!=null && leftC.operator=="==" && rightC.operator=="==") {
|
||||
if (leftC.right.constValue(program)?.number == 0.0 && rightC.right.constValue(program)?.number == 0.0) {
|
||||
val leftDt = leftC.left.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val rightDt = rightC.left.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(leftDt==rightDt && leftDt in IntegerDatatypes) {
|
||||
if (rightC.left.isSimple) {
|
||||
// x==0 or y==0 -> (x & y)==0
|
||||
// x==0 and y==0 -> (x | y)==0
|
||||
val newOperator = if(expr.operator=="or") "&" else "|"
|
||||
val inner = BinaryExpression(leftC.left, newOperator, rightC.left, expr.position)
|
||||
// the 'or' case cannot be easily optimized with a binary and like this!
|
||||
val inner = BinaryExpression(leftC.left, "|", rightC.left, expr.position)
|
||||
val compare = BinaryExpression(inner, "==", NumericLiteral(leftDt, 0.0, expr.position), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, compare, parent))
|
||||
}
|
||||
|
||||
@@ -277,6 +277,10 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
val target = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||
val arraysize = target?.arraysize?.constIndex()
|
||||
if(arraysize!=null) {
|
||||
if(arraysize+index < 0) {
|
||||
errors.err("index out of bounds", arrayIndexedExpression.position)
|
||||
return noModifications
|
||||
}
|
||||
// replace the negative index by the normal index
|
||||
val newIndex = NumericLiteral.optimalNumeric(arraysize+index, arrayIndexedExpression.indexer.position)
|
||||
arrayIndexedExpression.indexer.indexExpr = newIndex
|
||||
|
||||
@@ -108,6 +108,8 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"vtui/testvtui",
|
||||
"pcmaudio/play-adpcm",
|
||||
"pcmaudio/stream-wav",
|
||||
"pcmaudio/stream-simple-aflow",
|
||||
"pcmaudio/stream-simple-poll",
|
||||
"pcmaudio/vumeter",
|
||||
"sprites/dragon",
|
||||
"sprites/dragons",
|
||||
@@ -171,9 +173,9 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
"mandelbrot-gfx",
|
||||
"numbergame",
|
||||
"primes",
|
||||
"queens",
|
||||
"screencodes",
|
||||
"sincos",
|
||||
"sorting",
|
||||
"swirl",
|
||||
"swirl-float",
|
||||
"tehtriz",
|
||||
|
||||
@@ -96,11 +96,11 @@ class TestNumericLiteral: FunSpec({
|
||||
}
|
||||
|
||||
test("testEqualsRef") {
|
||||
(StringLiteral("hello", Encoding.PETSCII, dummyPos) == StringLiteral("hello", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
(StringLiteral("hello", Encoding.PETSCII, dummyPos) != StringLiteral("bye", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
(StringLiteral("hello", Encoding.SCREENCODES, dummyPos) == StringLiteral("hello", Encoding.SCREENCODES, dummyPos)) shouldBe true
|
||||
(StringLiteral("hello", Encoding.SCREENCODES, dummyPos) != StringLiteral("bye", Encoding.SCREENCODES, dummyPos)) shouldBe true
|
||||
(StringLiteral("hello", Encoding.SCREENCODES, dummyPos) != StringLiteral("hello", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
(StringLiteral.create("hello", Encoding.PETSCII, dummyPos) == StringLiteral.create("hello", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
(StringLiteral.create("hello", Encoding.PETSCII, dummyPos) != StringLiteral.create("bye", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
(StringLiteral.create("hello", Encoding.SCREENCODES, dummyPos) == StringLiteral.create("hello", Encoding.SCREENCODES, dummyPos)) shouldBe true
|
||||
(StringLiteral.create("hello", Encoding.SCREENCODES, dummyPos) != StringLiteral.create("bye", Encoding.SCREENCODES, dummyPos)) shouldBe true
|
||||
(StringLiteral.create("hello", Encoding.SCREENCODES, dummyPos) != StringLiteral.create("hello", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
|
||||
val lvOne = NumericLiteral(DataType.UBYTE, 1.0, dummyPos)
|
||||
val lvTwo = NumericLiteral(DataType.UBYTE, 2.0, dummyPos)
|
||||
|
||||
@@ -688,18 +688,6 @@ main {
|
||||
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
|
||||
}
|
||||
|
||||
test("no crash on sorting unused array") {
|
||||
val text="""
|
||||
main {
|
||||
ubyte[5] cards = [ 14, 6, 29, 16, 3 ]
|
||||
|
||||
sub start() {
|
||||
sort(cards)
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), true, text, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("no string error when inlining") {
|
||||
val text="""
|
||||
main {
|
||||
@@ -1059,4 +1047,30 @@ main {
|
||||
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("optimizing inlined functions must reference proper scopes") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
void other.sub1()
|
||||
cx16.r0L = other.sub1()+other.sub1()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
other {
|
||||
sub sub2() -> ubyte{
|
||||
cx16.r0++
|
||||
cx16.r1++
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
sub sub1() -> ubyte {
|
||||
return sub2()
|
||||
}
|
||||
}"""
|
||||
|
||||
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -3,15 +3,18 @@ package prog8tests
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.expectError
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.ast.expressions.CharLiteral
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.unescape
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
@@ -21,6 +24,7 @@ import prog8.code.target.encodings.IsoEncoding
|
||||
import prog8.code.target.encodings.PetsciiEncoding
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
import java.io.CharConversionException
|
||||
|
||||
|
||||
class TestStringEncodings: FunSpec({
|
||||
@@ -231,6 +235,22 @@ class TestStringEncodings: FunSpec({
|
||||
}
|
||||
}
|
||||
|
||||
context("kata") {
|
||||
test("kata translation to half width glyphs") {
|
||||
val orig = "カ が ガ"
|
||||
orig.length shouldBe 5
|
||||
val str = StringLiteral.create(orig, Encoding.KATAKANA, Position.DUMMY)
|
||||
str.value.length shouldBe 7
|
||||
|
||||
val character = CharLiteral.create('カ', Encoding.KATAKANA, Position.DUMMY)
|
||||
character.value shouldBe 'カ'
|
||||
|
||||
shouldThrow<CharConversionException> {
|
||||
CharLiteral.create('ガ', Encoding.KATAKANA, Position.DUMMY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("special pass-through") {
|
||||
val passthroughEscaped= """\x00\x1b\x99\xff"""
|
||||
val passthrough = passthroughEscaped.unescape()
|
||||
@@ -299,12 +319,22 @@ class TestStringEncodings: FunSpec({
|
||||
str string1 = "default"
|
||||
str string2 = sc:"screencodes"
|
||||
str string3 = iso:"iso"
|
||||
str string4 = petscii:"petscii"
|
||||
str string4 = iso5:"Хозяин и Работник"
|
||||
str string5 = iso16:"zażółć gęślą jaźń"
|
||||
str string6 = cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"
|
||||
str string7 = petscii:"petscii"
|
||||
str string8 = atascii:"atascii"
|
||||
str string9 = kata:"クジン。 # が # ガ"
|
||||
|
||||
ubyte char1 = 'd'
|
||||
ubyte char2 = sc:'s'
|
||||
ubyte char3 = iso:'i'
|
||||
ubyte char4 = petscii:'p'
|
||||
ubyte char4 = iso5:'и'
|
||||
ubyte char5 = iso16:'ł'
|
||||
ubyte char6 = cp437:'☺'
|
||||
ubyte char7 = petscii:'p'
|
||||
ubyte char8 = atascii:'p'
|
||||
ubyte char9 = kata:'カ'
|
||||
|
||||
sub start() {
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ main {
|
||||
}
|
||||
|
||||
test("ubyte to word casts") {
|
||||
var src="""
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared bb = 255
|
||||
|
||||
@@ -406,15 +406,6 @@ main {
|
||||
ub = zero+(all(warr) as ubyte)*1+zero
|
||||
txt.print_ub(ub)
|
||||
txt.nl()
|
||||
|
||||
sort(ubarr)
|
||||
sort(barr)
|
||||
sort(uwarr)
|
||||
sort(warr)
|
||||
reverse(ubarr)
|
||||
reverse(barr)
|
||||
reverse(uwarr)
|
||||
reverse(warr)
|
||||
}
|
||||
|
||||
sub floatingpoint() {
|
||||
@@ -449,7 +440,6 @@ main {
|
||||
txt.print_ub(ub)
|
||||
txt.nl()
|
||||
|
||||
reverse(flarr)
|
||||
for ub in 0 to len(flarr)-1 {
|
||||
floats.print(flarr[ub])
|
||||
txt.chrout(',')
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestProg8Parser: FunSpec( {
|
||||
context("Newline at end") {
|
||||
test("is not required - #40, fixed by #45") {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
val src = SourceCode.Text("foo {" + nl + "}") // source ends with '}' (= NO newline, issue #40)
|
||||
val src = SourceCode.Text("foo {$nl}") // source ends with '}' (= NO newline, issue #40)
|
||||
|
||||
// #40: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
|
||||
val module = parseModule(src)
|
||||
@@ -44,7 +44,7 @@ class TestProg8Parser: FunSpec( {
|
||||
|
||||
test("is still accepted - #40, fixed by #45") {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
|
||||
val srcText = "foo {$nl}$nl" // source does end with a newline (issue #40)
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
@@ -55,10 +55,10 @@ class TestProg8Parser: FunSpec( {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
|
||||
// BAD: 2nd block `bar` does NOT start on new line; however, there's is a nl at the very end
|
||||
val srcBad = "foo {" + nl + "}" + " bar {" + nl + "}" + nl
|
||||
val srcBad = "foo {$nl} bar {$nl}$nl"
|
||||
|
||||
// GOOD: 2nd block `bar` does start on a new line; however, a nl at the very end ain't needed
|
||||
val srcGood = "foo {" + nl + "}" + nl + "bar {" + nl + "}"
|
||||
val srcGood = "foo {$nl}${nl}bar {$nl}"
|
||||
|
||||
shouldThrow<ParseError> { parseModule(SourceCode.Text(srcBad)) }
|
||||
val module = parseModule(SourceCode.Text(srcGood))
|
||||
@@ -612,9 +612,9 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("testCharLiteralConstValue") {
|
||||
val char1 = CharLiteral('A', Encoding.PETSCII, Position.DUMMY)
|
||||
val char2 = CharLiteral('z', Encoding.SCREENCODES, Position.DUMMY)
|
||||
val char3 = CharLiteral('_', Encoding.ISO, Position.DUMMY)
|
||||
val char1 = CharLiteral.create('A', Encoding.PETSCII, Position.DUMMY)
|
||||
val char2 = CharLiteral.create('z', Encoding.SCREENCODES, Position.DUMMY)
|
||||
val char3 = CharLiteral.create('_', Encoding.ISO, Position.DUMMY)
|
||||
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, AsciiStringEncoder)
|
||||
char1.constValue(program).number.toInt() shouldBe 65
|
||||
@@ -640,8 +640,8 @@ class TestProg8Parser: FunSpec( {
|
||||
(ten <= ten) shouldBe true
|
||||
(ten < ten) shouldBe false
|
||||
|
||||
val abc = StringLiteral("abc", Encoding.PETSCII, Position.DUMMY)
|
||||
val abd = StringLiteral("abd", Encoding.PETSCII, Position.DUMMY)
|
||||
val abc = StringLiteral.create("abc", Encoding.PETSCII, Position.DUMMY)
|
||||
val abd = StringLiteral.create("abd", Encoding.PETSCII, Position.DUMMY)
|
||||
abc shouldBe abc
|
||||
(abc!=abd) shouldBe true
|
||||
(abc!=abc) shouldBe false
|
||||
|
||||
@@ -54,6 +54,11 @@ class TestSourceCode: AnnotationSpec() {
|
||||
shouldThrow<FileSystemException> { SourceCode.File(fixturesDir) }
|
||||
}
|
||||
|
||||
|
||||
private fun normalizeLineEndings(text: String): String {
|
||||
return text.replace("\\R".toRegex(), "\n")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingPath() {
|
||||
val filename = "ast_simple_main.p8"
|
||||
@@ -61,7 +66,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
src.origin shouldBe expectedOrigin
|
||||
src.text shouldBe path.toFile().readText()
|
||||
src.text shouldBe normalizeLineEndings(path.toFile().readText())
|
||||
src.isFromResources shouldBe false
|
||||
src.isFromFilesystem shouldBe true
|
||||
}
|
||||
@@ -85,7 +90,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
src.origin shouldBe expectedOrigin
|
||||
src.text shouldBe srcFile.readText()
|
||||
src.text shouldBe normalizeLineEndings(srcFile.readText())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -95,7 +100,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$LIBRARYFILEPREFIX/$pathString"
|
||||
src.text shouldBe srcFile.readText()
|
||||
src.text shouldBe normalizeLineEndings(srcFile.readText())
|
||||
src.isFromResources shouldBe true
|
||||
src.isFromFilesystem shouldBe false
|
||||
}
|
||||
@@ -107,7 +112,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$LIBRARYFILEPREFIX$pathString"
|
||||
src.text shouldBe srcFile.readText()
|
||||
src.text shouldBe normalizeLineEndings(srcFile.readText())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,7 +122,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$LIBRARYFILEPREFIX/$pathString"
|
||||
src.text shouldBe srcFile.readText()
|
||||
src.text shouldBe normalizeLineEndings(srcFile.readText())
|
||||
src.isFromResources shouldBe true
|
||||
}
|
||||
|
||||
@@ -128,7 +133,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$LIBRARYFILEPREFIX$pathString"
|
||||
src.text shouldBe srcFile.readText()
|
||||
src.text shouldBe normalizeLineEndings(srcFile.readText())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -138,7 +143,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
src.origin shouldBe "$LIBRARYFILEPREFIX/prog8lib/math.p8"
|
||||
src.text shouldBe srcFile.readText()
|
||||
src.text shouldBe normalizeLineEndings(srcFile.readText())
|
||||
src.isFromResources shouldBe true
|
||||
}
|
||||
|
||||
|
||||
@@ -535,7 +535,7 @@ main {
|
||||
byte @shared col = 20
|
||||
col++
|
||||
ubyte @shared ubb = lsb(col as uword)
|
||||
uword @shared vaddr = bottom[col] as uword << 8 ; a mkword will get inserted here
|
||||
uword @shared vaddr = bottom[cx16.r0L] as uword << 8 ; a mkword will get inserted here
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), optimize=true, src, writeAssembly=false)!!
|
||||
@@ -597,10 +597,10 @@ main {
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared a,b
|
||||
b = a ; works
|
||||
cx16.r1L = lsb(a) ; works
|
||||
funcw(a) ; works
|
||||
funcb(lsb(a)) ; fails :-( TODO FIX
|
||||
b = a
|
||||
cx16.r1L = lsb(a)
|
||||
funcw(a)
|
||||
funcb(lsb(a))
|
||||
}
|
||||
|
||||
sub funcw(uword arg) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.io.path.readText
|
||||
|
||||
@@ -220,5 +221,104 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("negative array index variables are not allowed, but ptr indexing is allowed") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared pointer
|
||||
ubyte[10] array
|
||||
str name = "hello"
|
||||
|
||||
byte sindex
|
||||
|
||||
array[sindex] = 10
|
||||
cx16.r0L = array[sindex]
|
||||
name[sindex] = 0
|
||||
cx16.r0L = name[sindex]
|
||||
pointer[sindex] = 10
|
||||
cx16.r0L=pointer[sindex]
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 4
|
||||
errors.errors[0] shouldContain "signed variables"
|
||||
errors.errors[1] shouldContain "signed variables"
|
||||
errors.errors[2] shouldContain "signed variables"
|
||||
errors.errors[3] shouldContain "signed variables"
|
||||
}
|
||||
|
||||
test("bounds checking for both positive and negative indexes, correct cases") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[10] array
|
||||
array[0] = 0
|
||||
array[9] = 0
|
||||
array[-1] = 0
|
||||
array[-10] = 0
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), false, src, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("bounds checking on strings, correct cases") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
str name = "1234567890"
|
||||
name[0] = 0
|
||||
name[9] = 0
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), false, src, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("bounds checking for positive indexes, invalid case") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[10] array
|
||||
array[10] = 0
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "out of bounds"
|
||||
}
|
||||
|
||||
test("bounds checking for negative indexes, invalid case") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[10] array
|
||||
array[-11] = 0
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "out of bounds"
|
||||
}
|
||||
|
||||
test("bounds checking on strings invalid cases") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
str name = "1234567890"
|
||||
name[10] = 0
|
||||
name[-1] = 0
|
||||
name[-11] = 0
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 3
|
||||
errors.errors[0] shouldContain "out of bounds"
|
||||
errors.errors[1] shouldContain "out of bounds"
|
||||
errors.errors[2] shouldContain "out of bounds"
|
||||
}
|
||||
})
|
||||
|
||||
@@ -11,9 +11,7 @@ import prog8.code.ast.PtAssignment
|
||||
import prog8.code.ast.PtBinaryExpression
|
||||
import prog8.code.ast.PtVariable
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.code.target.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.io.path.readText
|
||||
@@ -67,9 +65,10 @@ main {
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
|
||||
result.compilerAst.name shouldStartWith "on_the_fly"
|
||||
result.codegenAst!!.name shouldBe result.compilerAst.name
|
||||
result.codegenAst!!.children.size shouldBeGreaterThan 2
|
||||
val start = result.codegenAst!!.entrypoint()!!
|
||||
val ast = result.codegenAst!!
|
||||
ast.name shouldBe result.compilerAst.name
|
||||
ast.children.size shouldBeGreaterThan 2
|
||||
val start = ast.entrypoint()!!
|
||||
start.name shouldBe "p8s_start"
|
||||
start.children.size shouldBeGreaterThan 2
|
||||
val seed = start.children[0] as PtVariable
|
||||
@@ -363,4 +362,25 @@ main {
|
||||
assembly shouldContain "inlined routine follows: pop"
|
||||
assembly shouldContain "inlined routine follows: popw"
|
||||
}
|
||||
|
||||
test("syslib correctly available for raw outputs") {
|
||||
val text = """
|
||||
%output raw
|
||||
%launcher none
|
||||
%address ${'$'}2000
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
cx16.r0++
|
||||
sys.clear_carry()
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(Cx16Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(C128Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(PETTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(AtariTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large.
Load diff
@@ -11,6 +11,7 @@ import prog8.ast.statements.Assignment
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.intermediate.IRDataType
|
||||
import prog8.intermediate.IRFileReader
|
||||
import prog8.intermediate.IRSubroutine
|
||||
import prog8.intermediate.Opcode
|
||||
@@ -19,26 +20,6 @@ import prog8tests.helpers.compileText
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class TestCompilerVirtual: FunSpec({
|
||||
test("compile virtual: any all sort reverse builtin funcs") {
|
||||
val src = """
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
uword[] words = [1111,2222,0,4444,3333]
|
||||
bool result = all(words)
|
||||
cx16.r0++
|
||||
result = any(words)
|
||||
cx16.r0++
|
||||
sort(words)
|
||||
reverse(words)
|
||||
}
|
||||
}"""
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, true, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText())
|
||||
}
|
||||
|
||||
test("compile virtual: array with pointers") {
|
||||
val src = """
|
||||
main {
|
||||
@@ -475,6 +456,34 @@ main {
|
||||
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
|
||||
test("push() and pop() generate correct IR instructions") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte bb
|
||||
uword ww
|
||||
sys.push(42)
|
||||
bb++
|
||||
bb=sys.pop()
|
||||
sys.pushw(9999)
|
||||
ww++
|
||||
ww=sys.popw()
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), true, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
val irProgram = IRFileReader().read(virtfile)
|
||||
val start = irProgram.blocks[0].children[0] as IRSubroutine
|
||||
val instructions = start.chunks.flatMap { c->c.instructions }
|
||||
instructions.size shouldBe 13
|
||||
instructions[3].opcode shouldBe Opcode.PUSH
|
||||
instructions[3].type shouldBe IRDataType.BYTE
|
||||
instructions[5].opcode shouldBe Opcode.POP
|
||||
instructions[5].type shouldBe IRDataType.BYTE
|
||||
instructions[8].opcode shouldBe Opcode.PUSH
|
||||
instructions[8].type shouldBe IRDataType.WORD
|
||||
instructions[10].opcode shouldBe Opcode.POP
|
||||
instructions[10].type shouldBe IRDataType.WORD
|
||||
}
|
||||
|
||||
})
|
||||
@@ -23,7 +23,7 @@ compileTestKotlin {
|
||||
dependencies {
|
||||
implementation project(':codeCore')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "org.antlr:antlr4-runtime:4.13.1"
|
||||
implementation "org.antlr:antlr4-runtime:4.13.2"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
|
||||
implementation project(':parser')
|
||||
}
|
||||
|
||||
@@ -316,12 +316,12 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(array: ArrayLiteral) {
|
||||
outputListMembers(array.value.asSequence(), '[', ']')
|
||||
outputListMembers(array.value.asSequence())
|
||||
}
|
||||
|
||||
private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) {
|
||||
private fun outputListMembers(array: Sequence<Expression>) {
|
||||
var counter = 0
|
||||
output(openchar.toString())
|
||||
output("[")
|
||||
scopelevel++
|
||||
for (v in array) {
|
||||
v.accept(this)
|
||||
@@ -335,7 +335,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
}
|
||||
scopelevel--
|
||||
output(closechar.toString())
|
||||
output("]")
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
|
||||
@@ -364,10 +364,10 @@ private fun AssignmentContext.toAst(): Statement {
|
||||
}
|
||||
|
||||
val nestedAssign = assignment()
|
||||
if(nestedAssign==null)
|
||||
return Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
|
||||
return if(nestedAssign==null)
|
||||
Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
|
||||
else
|
||||
return ChainedAssignment(assign_target().toAst(), nestedAssign.toAst(), toPosition())
|
||||
ChainedAssignment(assign_target().toAst(), nestedAssign.toAst(), toPosition())
|
||||
}
|
||||
|
||||
private fun AugassignmentContext.toAst(): Assignment {
|
||||
|
||||
@@ -10,6 +10,8 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.JapaneseCharacterConverter
|
||||
import java.io.CharConversionException
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.floor
|
||||
@@ -563,12 +565,12 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
override fun hashCode(): Int = Objects.hash(type, number)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is NumericLiteral)
|
||||
return false
|
||||
return if(other==null || other !is NumericLiteral)
|
||||
false
|
||||
else if(type!=DataType.BOOL && other.type!=DataType.BOOL)
|
||||
return number==other.number
|
||||
number==other.number
|
||||
else
|
||||
return type==other.type && number==other.number
|
||||
type==other.type && number==other.number
|
||||
}
|
||||
|
||||
operator fun compareTo(other: NumericLiteral): Int = number.compareTo(other.number)
|
||||
@@ -713,7 +715,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
|
||||
}
|
||||
}
|
||||
|
||||
class CharLiteral(val value: Char,
|
||||
class CharLiteral private constructor(val value: Char,
|
||||
var encoding: Encoding,
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
@@ -723,9 +725,20 @@ class CharLiteral(val value: Char,
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(character: Char, encoding: Encoding, position: Position): CharLiteral {
|
||||
if(encoding==Encoding.KATAKANA) {
|
||||
val processed = JapaneseCharacterConverter.zenkakuKatakanaToHankakuKatakana(character.toString())
|
||||
if(processed.length==1)
|
||||
return CharLiteral(processed[0], encoding, position)
|
||||
else
|
||||
throw CharConversionException("character literal encodes into multiple bytes at $position")
|
||||
} else
|
||||
return CharLiteral(character, encoding, position)
|
||||
}
|
||||
|
||||
fun fromEscaped(raw: String, encoding: Encoding, position: Position): CharLiteral {
|
||||
val unescaped = raw.unescape()
|
||||
return CharLiteral(unescaped[0], encoding, position)
|
||||
return create(unescaped[0], encoding, position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,7 +769,7 @@ class CharLiteral(val value: Char,
|
||||
}
|
||||
}
|
||||
|
||||
class StringLiteral(val value: String,
|
||||
class StringLiteral private constructor(val value: String,
|
||||
var encoding: Encoding,
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
@@ -766,10 +779,15 @@ class StringLiteral(val value: String,
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromEscaped(raw: String, encoding: Encoding, position: Position): StringLiteral {
|
||||
val unescaped = raw.unescape()
|
||||
return StringLiteral(unescaped, encoding, position)
|
||||
fun create(str: String, encoding: Encoding, position: Position): StringLiteral {
|
||||
if (encoding == Encoding.KATAKANA) {
|
||||
val processed = JapaneseCharacterConverter.zenkakuKatakanaToHankakuKatakana(str)
|
||||
return StringLiteral(processed, encoding, position)
|
||||
} else
|
||||
return StringLiteral(str, encoding, position)
|
||||
}
|
||||
|
||||
fun fromEscaped(raw: String, encoding: Encoding, position: Position): StringLiteral = create(raw.unescape(), encoding, position)
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
|
||||
Prerelease version from git commit 42f4b06a in branch master
|
||||
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-atari.p8
|
||||
@@ -264,7 +263,7 @@ diskio {
|
||||
f_open_w (uword filenameptr) -> bool
|
||||
f_read (uword bufferpointer, uword num_bytes) -> uword
|
||||
f_read_all (uword bufferpointer) -> uword
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
|
||||
f_write (uword bufferpointer, uword num_bytes) -> bool
|
||||
lf_end_list ()
|
||||
lf_next_entry () -> bool
|
||||
@@ -308,6 +307,7 @@ string {
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
|
||||
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
|
||||
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
|
||||
rstrip (str s)
|
||||
rtrim (str s)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
|
||||
Prerelease version from git commit 42f4b06a in branch master
|
||||
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-c128.p8
|
||||
@@ -490,7 +489,7 @@ diskio {
|
||||
f_open_w (uword filenameptr) -> bool
|
||||
f_read (uword bufferpointer, uword num_bytes) -> uword
|
||||
f_read_all (uword bufferpointer) -> uword
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
|
||||
f_write (uword bufferpointer, uword num_bytes) -> bool
|
||||
lf_end_list ()
|
||||
lf_next_entry () -> bool
|
||||
@@ -534,6 +533,7 @@ string {
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
|
||||
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
|
||||
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
|
||||
rstrip (str s)
|
||||
rtrim (str s)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
|
||||
Prerelease version from git commit 42f4b06a in branch master
|
||||
|
||||
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-c64.p8
|
||||
@@ -491,7 +491,7 @@ diskio {
|
||||
f_open_w (uword filenameptr) -> bool
|
||||
f_read (uword bufferpointer, uword num_bytes) -> uword
|
||||
f_read_all (uword bufferpointer) -> uword
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
|
||||
f_write (uword bufferpointer, uword num_bytes) -> bool
|
||||
lf_end_list ()
|
||||
lf_next_entry () -> bool
|
||||
@@ -535,6 +535,7 @@ string {
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
|
||||
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
|
||||
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
|
||||
rstrip (str s)
|
||||
rtrim (str s)
|
||||
@@ -609,9 +610,12 @@ floats {
|
||||
SQRA () -> clobbers (A,X,Y) = $bf74
|
||||
TAN () -> clobbers (A,X,Y) = $e2b4
|
||||
atan (float value) -> float
|
||||
atan2 (float y, float x) -> float
|
||||
ceil (float value) -> float
|
||||
clampf (float value, float minimum, float maximum) -> float
|
||||
cos (float angle) -> float
|
||||
cot (float value) -> float
|
||||
csc (float value) -> float
|
||||
deg (float angle) -> float
|
||||
floor (float value) -> float
|
||||
ln (float value) -> float
|
||||
@@ -628,6 +632,7 @@ floats {
|
||||
rnd () -> float
|
||||
rndseed (float seed)
|
||||
round (float value) -> float
|
||||
secant (float value) -> float
|
||||
sin (float angle) -> float
|
||||
tan (float value) -> float
|
||||
tostr (float value @FAC1) -> clobbers (X) -> str @AY
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
|
||||
Prerelease version from git commit 42f4b06a in branch master
|
||||
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-cx16.p8
|
||||
@@ -69,12 +68,14 @@ diskio {
|
||||
f_open_w_seek (str filename) -> bool
|
||||
f_read (uword bufferpointer, uword num_bytes) -> uword
|
||||
f_read_all (uword bufferpointer) -> uword
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
|
||||
f_seek (uword pos_hiword, uword pos_loword)
|
||||
f_seek_w (uword pos_hiword, uword pos_loword)
|
||||
f_tell () -> uword @R0, uword @R1, uword @R2, uword @R3
|
||||
f_write (uword bufferpointer, uword num_bytes) -> bool
|
||||
fastmode (ubyte mode) -> bool
|
||||
internal_f_open_w (str filename, bool open_for_seeks) -> bool
|
||||
internal_f_tell ()
|
||||
internal_load_routine (uword filenameptr, uword address_override, bool headerless) -> uword
|
||||
internal_save_routine (uword filenameptr, uword startaddress, uword savesize, bool headerless) -> bool
|
||||
lf_end_list ()
|
||||
@@ -132,6 +133,7 @@ txt {
|
||||
iso16 ()
|
||||
iso5 ()
|
||||
iso_off ()
|
||||
kata ()
|
||||
lowercase ()
|
||||
nl ()
|
||||
petscii2scr (ubyte petscii_char @A) -> ubyte @A
|
||||
@@ -169,6 +171,35 @@ txt {
|
||||
LIBRARY MODULE NAME: syslib
|
||||
---------------------------
|
||||
cbm {
|
||||
&uword CBINV
|
||||
&uword CINV
|
||||
&uword IBASIN
|
||||
&uword IBSOUT
|
||||
&uword ICHKIN
|
||||
&uword ICKOUT
|
||||
&uword ICLALL
|
||||
&uword ICLOSE
|
||||
&uword ICLRCH
|
||||
&uword ICRNCH
|
||||
&uword IERROR
|
||||
&uword IEVAL
|
||||
&uword IGETIN
|
||||
&uword IGONE
|
||||
&uword ILOAD
|
||||
&uword IMAIN
|
||||
&uword IOPEN
|
||||
&uword IQPLOP
|
||||
&uword IRQ_VEC
|
||||
&uword ISAVE
|
||||
&uword ISTOP
|
||||
&uword NMINV
|
||||
&uword NMI_VEC
|
||||
&uword RESET_VEC
|
||||
&ubyte SAREG
|
||||
&ubyte SPREG
|
||||
&ubyte SXREG
|
||||
&ubyte SYREG
|
||||
&uword USRADD
|
||||
ACPTR () -> ubyte @A = $ffa5
|
||||
CHKIN (ubyte logical @X) -> clobbers (A,X) -> bool @Pc = $ffc6
|
||||
CHKOUT (ubyte logical @X) -> clobbers (A,X) = $ffc9
|
||||
@@ -217,8 +248,6 @@ cbm {
|
||||
}
|
||||
|
||||
cx16 {
|
||||
&uword CBINV
|
||||
&uword CINV
|
||||
const ubyte EXTAPI16_stack_enter_kernal_stack
|
||||
const ubyte EXTAPI16_stack_leave_kernal_stack
|
||||
const ubyte EXTAPI16_stack_pop
|
||||
@@ -236,34 +265,8 @@ cx16 {
|
||||
const ubyte EXTAPI_ps2data_fetch
|
||||
const ubyte EXTAPI_ps2data_raw
|
||||
const ubyte EXTAPI_ps2kbd_typematic
|
||||
&uword IBASIN
|
||||
&uword IBSOUT
|
||||
&uword ICHKIN
|
||||
&uword ICKOUT
|
||||
&uword ICLALL
|
||||
&uword ICLOSE
|
||||
&uword ICLRCH
|
||||
&uword ICRNCH
|
||||
&uword IERROR
|
||||
&uword IEVAL
|
||||
&uword IGETIN
|
||||
&uword IGONE
|
||||
&uword ILOAD
|
||||
&uword IMAIN
|
||||
&uword IOPEN
|
||||
&uword IQPLOP
|
||||
&uword IRQ_VEC
|
||||
&uword ISAVE
|
||||
&uword ISTOP
|
||||
const ubyte EXTAPI_scnsiz
|
||||
&uword KEYHDL
|
||||
&uword NMINV
|
||||
&uword NMI_VEC
|
||||
&uword RESET_VEC
|
||||
&ubyte SAREG
|
||||
&ubyte SPREG
|
||||
&ubyte SXREG
|
||||
&ubyte SYREG
|
||||
&uword USRADD
|
||||
&uword VERA_ADDR
|
||||
&ubyte VERA_ADDR_H
|
||||
&ubyte VERA_ADDR_L
|
||||
@@ -441,6 +444,7 @@ cx16 {
|
||||
&word r9s
|
||||
&byte r9sH
|
||||
&byte r9sL
|
||||
&ubyte stavec
|
||||
&ubyte via1acr
|
||||
&ubyte via1ddra
|
||||
&ubyte via1ddrb
|
||||
@@ -534,7 +538,7 @@ cx16 {
|
||||
enter_basic (bool cold_or_warm @Pc) -> clobbers (A,X,Y) = $ff47
|
||||
entropy_get () -> ubyte @A, ubyte @X, ubyte @Y = $fecf
|
||||
extapi (ubyte callnumber @A) -> clobbers (A,X,Y) = $feab
|
||||
fetch (ubyte bank @X, ubyte index @Y) -> clobbers (X) -> ubyte @A = $ff74
|
||||
fetch (ubyte zp_startaddr @A, ubyte bank @X, ubyte index @Y) -> clobbers (X) -> ubyte @A = $ff74
|
||||
get_chrin_keyhandler () -> ubyte @R0, uword @R1
|
||||
get_program_args (uword buffer @R0, ubyte buf_size @R1, bool binary @Pc)
|
||||
get_screen_mode () -> ubyte @A, ubyte @X, ubyte @Y
|
||||
@@ -559,9 +563,9 @@ cx16 {
|
||||
monitor () -> clobbers (A,X,Y) = $fecc
|
||||
mouse_config (byte shape @A, ubyte resX @X, ubyte resY @Y) -> clobbers (A,X,Y) = $ff68
|
||||
mouse_config2 (byte shape @A) -> clobbers (A,X,Y)
|
||||
mouse_get (ubyte zpdataptr @X) -> ubyte @A = $ff6b
|
||||
mouse_get (ubyte zdataptr @X) -> ubyte @A, byte @X = $ff6b
|
||||
mouse_get_sprite_offset () -> clobbers (A,X,Y) -> word @R0, word @R1
|
||||
mouse_pos () -> clobbers (X) -> ubyte @A, word @R0, word @R1
|
||||
mouse_pos () -> ubyte @A, uword @R0, uword @R1, byte @X
|
||||
mouse_scan () -> clobbers (A,X,Y) = $ff71
|
||||
mouse_set_pos (uword xpos @R0, uword ypos @R1) -> clobbers (X)
|
||||
mouse_set_sprite_offset (word xoffset @R0, word yoffset @R1) -> clobbers (A,X,Y)
|
||||
@@ -598,9 +602,11 @@ cx16 {
|
||||
reset_system ()
|
||||
restore_vera_context () -> clobbers (A)
|
||||
restore_virtual_registers () -> clobbers (A,Y)
|
||||
rom_version () -> clobbers (Y) -> ubyte @A, bool @Pc
|
||||
rombank (ubyte bank @A)
|
||||
save_vera_context () -> clobbers (A)
|
||||
save_virtual_registers () -> clobbers (A,Y)
|
||||
scnsiz (ubyte width @X, ubyte heigth @Y) -> clobbers (A,X,Y)
|
||||
screen_mode (ubyte mode @A, bool getCurrent @Pc) -> ubyte @A, ubyte @X, ubyte @Y, bool @Pc = $ff5f
|
||||
screen_set_charset (ubyte charset @A, uword charsetptr @XY) -> clobbers (A,X,Y) = $ff62
|
||||
search_x16edit () -> ubyte
|
||||
@@ -741,6 +747,7 @@ string {
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
|
||||
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
|
||||
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
|
||||
rstrip (str s)
|
||||
rtrim (str s)
|
||||
@@ -856,9 +863,12 @@ floats {
|
||||
VAL_1 (uword string @XY, ubyte length @A) -> clobbers (A,X,Y) -> float @FAC1 = $fe09
|
||||
ZEROFC () -> clobbers (A,X,Y) = $fe72
|
||||
atan (float value) -> float
|
||||
atan2 (float y, float x) -> float
|
||||
ceil (float value) -> float
|
||||
clampf (float value, float minimum, float maximum) -> float
|
||||
cos (float angle) -> float
|
||||
cot (float value) -> float
|
||||
csc (float value) -> float
|
||||
deg (float angle) -> float
|
||||
floor (float value) -> float
|
||||
ln (float value) -> float
|
||||
@@ -875,6 +885,7 @@ floats {
|
||||
rnd () -> float
|
||||
rndseed (float seed)
|
||||
round (float value) -> float
|
||||
secant (float value) -> float
|
||||
sin (float angle) -> float
|
||||
tan (float value) -> float
|
||||
tostr (float value @FAC1) -> clobbers (X) -> str @AY
|
||||
@@ -1077,7 +1088,8 @@ sprites {
|
||||
data (ubyte spritenum, ubyte bank, uword addr)
|
||||
flipx (ubyte spritenum, bool flipped)
|
||||
flipy (ubyte spritenum, bool flipped)
|
||||
get_data_ptr (ubyte spritenum)
|
||||
get_data_ptr (ubyte spritenum @A) -> ubyte @R1, uword @R0
|
||||
get_data_ptr_internal (ubyte spritenum)
|
||||
getx (ubyte spritenum) -> word
|
||||
gety (ubyte spritenum) -> word
|
||||
hide (ubyte spritenum)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
|
||||
Prerelease version from git commit 42f4b06a in branch master
|
||||
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-pet32.p8
|
||||
@@ -281,7 +280,7 @@ diskio {
|
||||
f_open_w (uword filenameptr) -> bool
|
||||
f_read (uword bufferpointer, uword num_bytes) -> uword
|
||||
f_read_all (uword bufferpointer) -> uword
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
|
||||
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
|
||||
f_write (uword bufferpointer, uword num_bytes) -> bool
|
||||
lf_end_list ()
|
||||
lf_next_entry () -> bool
|
||||
@@ -325,6 +324,7 @@ string {
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
|
||||
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
|
||||
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
|
||||
rstrip (str s)
|
||||
rtrim (str s)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
|
||||
Prerelease version from git commit 42f4b06a in branch master
|
||||
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-virtual.p8
|
||||
@@ -114,6 +113,7 @@ LIBRARY MODULE NAME: syslib
|
||||
sys {
|
||||
const ubyte target
|
||||
clear_carry ()
|
||||
clear_irqd ()
|
||||
disable_caseswitch ()
|
||||
enable_caseswitch ()
|
||||
exit (ubyte returnvalue)
|
||||
@@ -134,6 +134,7 @@ sys {
|
||||
restore_prog8_internals ()
|
||||
save_prog8_internals ()
|
||||
set_carry ()
|
||||
set_irqd ()
|
||||
wait (uword jiffies)
|
||||
waitvsync ()
|
||||
}
|
||||
@@ -258,9 +259,12 @@ floats {
|
||||
const float TWOPI
|
||||
const float π
|
||||
atan (float value) -> float
|
||||
atan2 (float y, float x) -> float
|
||||
ceil (float value) -> float
|
||||
clampf (float value, float minimum, float maximum) -> float
|
||||
cos (float angle) -> float
|
||||
cot (float value) -> float
|
||||
csc (float value) -> float
|
||||
deg (float angle) -> float
|
||||
floor (float value) -> float
|
||||
ln (float value) -> float
|
||||
@@ -277,6 +281,7 @@ floats {
|
||||
rnd () -> float
|
||||
rndseed (float seed)
|
||||
round (float value) -> float
|
||||
secant (float value) -> float
|
||||
sin (float angle) -> float
|
||||
tan (float value) -> float
|
||||
tostr (float value) -> str
|
||||
@@ -377,6 +382,7 @@ string {
|
||||
lstripped (str s) -> str
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
rfind (uword stringptr, ubyte character) -> ubyte
|
||||
right (str source, ubyte slen, str target)
|
||||
rstrip (str s)
|
||||
rtrim (str s)
|
||||
|
||||
+16
-15
@@ -10,27 +10,27 @@ Prog8 documentation - |version|
|
||||
What is Prog8?
|
||||
--------------
|
||||
|
||||
This is a compiled programming language targeting the 8-bit
|
||||
`6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
|
||||
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
|
||||
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
|
||||
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
|
||||
such as the `Commodore 64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
|
||||
This is a compiled programming language targeting the 8-bit 6502 CPU family.
|
||||
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
|
||||
while still being low level enough to create high performance programs.
|
||||
You can compile programs for various machines with this CPU:
|
||||
|
||||
* Commander X16 (with 6502 cpu, 65816 cpu specifics are currently not supported)
|
||||
They targeted CPUs are the `6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
|
||||
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
|
||||
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
|
||||
They are from the late 1970's and early 1980's and was used in many home computers from that era,
|
||||
such as the `Commodore 64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
|
||||
|
||||
You can compile programs for various machines:
|
||||
|
||||
* Commander X16 (with 65c02 cpu, 65816 cpu specifics are currently not supported)
|
||||
* Commodore 64
|
||||
* Commodore 128 (limited support)
|
||||
* Commodore PET (limited support)
|
||||
* Atari 800 XL (limited support)
|
||||
|
||||
The source code is on github: https://github.com/irmen/prog8.git
|
||||
|
||||
|
||||
Software License
|
||||
^^^^^^^^^^^^^^^^
|
||||
Open source Software License
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Full source code is on github: https://github.com/irmen/prog8.git
|
||||
Prog8 is copyright © Irmen de Jong (irmen@razorvine.net | http://www.razorvine.net).
|
||||
|
||||
This is free software, as defined in the GNU GPL 3.0 (https://www.gnu.org/licenses/gpl.html)
|
||||
@@ -71,7 +71,8 @@ Features
|
||||
|
||||
- it is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...)
|
||||
- the compiled programs run very fast, because compilation to highly efficient native machine code.
|
||||
- Provides a convenient and fast edit/compile/run cycle by being able to directly launch
|
||||
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
|
||||
- provides a convenient and fast edit/compile/run cycle by being able to directly launch
|
||||
the compiled program in an emulator and provide debugging information to this emulator.
|
||||
- the language looks like a mix of Python and C so should be quite easy to learn
|
||||
- Modular programming, scoping via modules, code blocks, and subroutines. No need for forward declarations.
|
||||
@@ -84,7 +85,7 @@ Features
|
||||
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
|
||||
- ``when`` statement to avoid if-else chains
|
||||
- ``in`` expression for concise and efficient multi-value/containment test
|
||||
- Several powerful built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``, ``sort`` and ``reverse``
|
||||
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
|
||||
- Variable data types include signed and unsigned bytes and words, arrays, strings.
|
||||
- Various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- Floating point math is supported on select compiler targets.
|
||||
|
||||
+228
-13
@@ -1,10 +1,11 @@
|
||||
===============
|
||||
Library modules
|
||||
===============
|
||||
=====================================
|
||||
Library modules and builtin functions
|
||||
=====================================
|
||||
|
||||
The compiler provides several "built-in" library modules with useful subroutine and variables.
|
||||
The compiler provides several library modules with useful subroutine and variables.
|
||||
There are also a bunch of builtin functions.
|
||||
|
||||
Some of these may be specific for a certain compilation target, or work slightly different,
|
||||
Some of the libraries may be specific for a certain compilation target, or work slightly different,
|
||||
but some effort is put into making them available across compilation targets.
|
||||
|
||||
This means that as long as your program is only using the subroutines from these
|
||||
@@ -29,6 +30,193 @@ of these library modules automatically as required.
|
||||
code publicly available on https://www.codebase64.org/
|
||||
|
||||
|
||||
.. _builtinfunctions:
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
There's a set of predefined functions in the language. These are fixed and can't be redefined in user code.
|
||||
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
|
||||
|
||||
|
||||
Math
|
||||
^^^^
|
||||
|
||||
abs (x)
|
||||
Returns the absolute value of a number (integer or floating point).
|
||||
|
||||
min (x, y)
|
||||
Returns the smallest of x and y. Supported for integer types only, for floats use ``floats.minf()`` instead.
|
||||
|
||||
max (x, y)
|
||||
Returns the largest of x and y. Supported for integer types only, for floats use ``floats.maxf()`` instead.
|
||||
|
||||
clamp (value, minimum, maximum)
|
||||
Returns the value restricted to the given minimum and maximum.
|
||||
Supported for integer types only, for floats use ``floats.clampf()`` instead.
|
||||
|
||||
sgn (x)
|
||||
Get the sign of the value (integer or floating point).
|
||||
The result is a byte: -1, 0 or 1 (negative, zero, positive).
|
||||
|
||||
sqrt (w)
|
||||
Returns the square root of the number.
|
||||
Supports unsigned integer (result is ubyte) and floating point numbers.
|
||||
To do the reverse - squaring a number - just write ``x*x``.
|
||||
|
||||
divmod (dividend, divisor, quotient, remainder)
|
||||
Performs division only once and returns both quotient and remainder in a single call, where using '/' and '%' separately
|
||||
would perform the division operation twice.
|
||||
All values are ubytes or all are uwords.
|
||||
The last two arguments must be variables to receive the quotient and remainder results, respectively.
|
||||
|
||||
|
||||
Array operations
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
len (x)
|
||||
Number of values in the array value x, or the number of characters in a string (excluding the 0-byte).
|
||||
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
|
||||
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
|
||||
length of the string during execution, the value of len(s) may no longer be correct!
|
||||
(use the ``string.length`` routine if you want to dynamically determine the length by counting to the
|
||||
first 0-byte)
|
||||
|
||||
|
||||
Miscellaneous
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
cmp (x,y)
|
||||
Compare the integer value x to integer value y. Doesn't return a value or boolean result, only sets the processor's status bits!
|
||||
You can use a conditional jumps (``if_cc`` etcetera) to act on this.
|
||||
Normally you should just use a comparison expression (``x < y``)
|
||||
|
||||
lsb (x)
|
||||
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
|
||||
|
||||
msb (x)
|
||||
Get the most significant byte of the word x.
|
||||
|
||||
mkword (msb, lsb)
|
||||
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
|
||||
So mkword($80, $22) results in $8022.
|
||||
|
||||
.. note::
|
||||
The arguments to the mkword() function are in 'natural' order that is first the msb then the lsb.
|
||||
Don't get confused by how the system actually stores this 16-bit word value in memory (which is
|
||||
in little-endian format, so lsb first then msb)
|
||||
|
||||
peek (address)
|
||||
same as @(address) - reads the byte at the given address in memory.
|
||||
|
||||
peekw (address)
|
||||
reads the word value at the given address in memory. Word is read as usual little-endian lsb/msb byte order.
|
||||
|
||||
peekf (address)
|
||||
reads the float value at the given address in memory. On CBM machines, this reads 5 bytes.
|
||||
|
||||
poke (address, value)
|
||||
same as @(address)=value - writes the byte value at the given address in memory.
|
||||
|
||||
pokew (address, value)
|
||||
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
|
||||
|
||||
pokef (address, value)
|
||||
writes the float value at the given address in memory. On CBM machines, this writes 5 bytes.
|
||||
|
||||
pokemon (address, value)
|
||||
Like poke(), but also returns the previous value in the given address.
|
||||
Also doesn't have anything to do with a certain video game.
|
||||
|
||||
rol (x)
|
||||
Rotate the bits in x (byte or word) one position to the left.
|
||||
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
|
||||
while the highest bit will become the new Carry flag value.
|
||||
(essentially, it is a 9-bit or 17-bit rotation)
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can rol a memory location directly by using the direct memory access syntax, so like ``rol(@($5000))``
|
||||
|
||||
rol2 (x)
|
||||
Like ``rol`` but now as 8-bit or 16-bit rotation.
|
||||
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can rol a memory location directly by using the direct memory access syntax, so like ``rol2(@($5000))``
|
||||
|
||||
ror (x)
|
||||
Rotate the bits in x (byte or word) one position to the right.
|
||||
This uses the CPU's rotate semantics: the highest bit will be set to the current value of the Carry flag,
|
||||
while bit 0 will become the new Carry flag value.
|
||||
(essentially, it is a 9-bit or 17-bit rotation)
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can ror a memory location directly by using the direct memory access syntax, so like ``ror(@($5000))``
|
||||
|
||||
ror2 (x)
|
||||
Like ``ror`` but now as 8-bit or 16-bit rotation.
|
||||
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can ror a memory location directly by using the direct memory access syntax, so like ``ror2(@($5000))``
|
||||
|
||||
setlsb (x, value)
|
||||
Sets the least significant byte of word variable x to a new value. Leaves the MSB untouched.
|
||||
|
||||
setmsb (x, value)
|
||||
Sets the most significant byte of word variable x to a new value. Leaves the LSB untouched.
|
||||
|
||||
sizeof (name) ; sizeof (number)
|
||||
Number of bytes that the object 'name', or the number 'number' occupies in memory.
|
||||
This is a constant determined by the data type of
|
||||
the object. For instance, for a variable of type uword, the sizeof is 2.
|
||||
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
|
||||
Note: usually you will be interested in the number of elements in an array, use len() for that.
|
||||
|
||||
memory (name, size, alignment)
|
||||
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
|
||||
with the given name. The block is uninitialized memory, it is *not* set to zero!
|
||||
If you specify an alignment value >1, it means the block of memory will
|
||||
be aligned to such a dividable address in memory, for instance an alignment of $100 means the
|
||||
memory block is aligned on a page boundary, and $2 means word aligned (even addresses).
|
||||
Requesting the address of such a named memory block again later with
|
||||
the same name, will result in the same address as before.
|
||||
When reusing blocks in that way, it is required that the size argument is the same,
|
||||
otherwise you'll get a compilation error.
|
||||
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
|
||||
not suffice; for instance if you need more than 256 consecutive bytes.
|
||||
The return value is just a simple uword address so it cannot be used as an array in your program.
|
||||
You can only treat it as a pointer or use it in inline assembly.
|
||||
|
||||
call (address) -> uword
|
||||
Calls a subroutine given by its memory address. You cannot pass arguments directly,
|
||||
although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example.
|
||||
It is assumed the subroutine returns a word value (in AY), if it does not, just add void to the call to ignore the result value.
|
||||
This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable.
|
||||
But because it doesn't handle bank switching etcetera by itself,
|
||||
it is a lot faster than ``callfar``. And it works on other systems than just the Commander X16.
|
||||
|
||||
callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now
|
||||
Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine)
|
||||
Be aware that ram OR rom bank may be changed depending on the address it jumps to!
|
||||
The argumentword will be loaded into the A+Y registers before calling the routine.
|
||||
The uword value that the routine returns in the A+Y registers, will be returned.
|
||||
NOTE: this routine is very inefficient, so don't use it to call often. Set the bank yourself
|
||||
or even write a custom tailored trampoline routine if you need to. Or use ``call`` if you can.
|
||||
|
||||
syscall (callnr), syscall1 (callnr, arg), syscall2 (callnr, arg1, arg2), syscall3 (callnr, arg1, arg2, arg3)
|
||||
Functions for doing a system call on targets that support this. Currently no actual target
|
||||
uses this though except, possibly, the experimental code generation target!
|
||||
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
|
||||
specific memory locations. So these builtin function calls are not useful yet except for
|
||||
experimentation in new code generation targets.
|
||||
|
||||
rsave
|
||||
Saves all registers including status (or only X) on the stack
|
||||
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved,
|
||||
but you can use ``cx16.save_virtual_registers()`` for that.
|
||||
|
||||
rrestore
|
||||
Restore all registers including status (or only X) back from the cpu hardware stack
|
||||
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* restored,
|
||||
but you can use ``cx16.restore_virtual_registers()`` for that.
|
||||
|
||||
|
||||
Low-fi variable and subroutine definitions in all available library modules
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
@@ -175,6 +363,25 @@ sys (part of syslib)
|
||||
Low-level function that should normally not be used.
|
||||
|
||||
|
||||
anyall
|
||||
------
|
||||
Routines to check if any or all values in an array or memory buffer are not zero.
|
||||
|
||||
``any (arrayptr, num_elements)``
|
||||
true if any of the byte values in the array is not zero, else false.
|
||||
|
||||
``all (arrayptr, num_elements)``
|
||||
true if all of the byte values in the array are not zero, else false.
|
||||
|
||||
``anyw (arrayptr, num_elements)``
|
||||
true if any of the word values in the array is not zero, else false.
|
||||
Doesn't work on split arrays.
|
||||
|
||||
``allw (arrayptr, num_elements)``
|
||||
true if all of the word values in the array are not zero, else false.
|
||||
Doesn't work on split arrays.
|
||||
|
||||
|
||||
conv
|
||||
----
|
||||
Routines to convert strings to numbers or vice versa.
|
||||
@@ -228,6 +435,12 @@ and to seek in open files.
|
||||
Read the `diskio source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/diskio.p8>`_
|
||||
to see what's in there. (Note: slight variations for different compiler targets)
|
||||
|
||||
.. note::
|
||||
For simplicity sake, this library is designed to work on a *single* open file
|
||||
for reading, and a *single* open file for writing at any time only.
|
||||
If you need to load or save to more than one file at a time, you'll have
|
||||
to write your own I/O routines (or supplement the ones found here)
|
||||
|
||||
.. note::
|
||||
If you are using the X16 emulator with HostFS, and are experiencing weird behavior with these
|
||||
routines, please first try again with an SD-card image instead of HostFs.
|
||||
@@ -270,14 +483,15 @@ Provides string manipulation routines.
|
||||
Also, you have to make sure yourself that start and length are within bounds of the strings.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
``find (string, char) -> ubyte index + carry bit``
|
||||
Locates the first position of the given character in the string, returns carry bit set if found
|
||||
and the index in the string. Or 0+carry bit clear if the character was not found.
|
||||
``find (string, char) -> ubyte index, bool found``
|
||||
Locates the first index of the given character in the string, and a boolean (in Carry flag)
|
||||
to say if it was found at all. If the character is not found, index 255 (and false) is returned.
|
||||
You can consider this a safer way of checking if a character occurs
|
||||
in a string than using an `in` containment check - because the find routine
|
||||
properly stops at the first 0-byte string terminator it encounters.
|
||||
Simply call this and only act on the carry status with ``if_cc`` for example.
|
||||
Much like the difference between len(str) and length(str).
|
||||
in a string than using an `in` containment check - because this find routine
|
||||
properly stops at the first 0-byte string terminator it encounters in case the string was modified.
|
||||
|
||||
``rfind (string, char) -> ubyte index, bool found``
|
||||
Like ``find``, but now looking from the *right* of the string instead.
|
||||
|
||||
``contains (string, char) -> bool``
|
||||
Just returns true if the character is in the given string, or false if it's not.
|
||||
@@ -765,7 +979,8 @@ the emulators already support it).
|
||||
This routine is about 50% faster as a regular byte-by-byte copy.
|
||||
|
||||
``transparency``
|
||||
Enable or disable transparent writes (color 0 will be transparent if enabled).
|
||||
Set transparent write mode for VeraFX cached writes and also for normal writes to DATA0/DATA.
|
||||
If enabled, pixels with value 0 do not modify VRAM when written (so they are "transparent")
|
||||
|
||||
Read the `verafx source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/verafx.p8>`_
|
||||
to see what's in there.
|
||||
|
||||
+10
-227
@@ -337,21 +337,20 @@ An uword variable can be used in limited scenarios as a 'pointer' to a byte in m
|
||||
dynamic, location. You can use array indexing on a pointer variable to use it as a byte array at
|
||||
a dynamic location in memory: currently this is equivalent to directly referencing the bytes in
|
||||
memory at the given index. In contrast to a real array variable, the index value can be the size of a word.
|
||||
Unlike array variables, you cannot use a negative index to count from the end, because the size of the array is unknown.
|
||||
Unlike array variables, negative indexing for pointer variables does *not* mean it will be counting from the end, because the size of the buffer is unknown.
|
||||
Instead, it simply addresses memory that lies *before* the pointer variable.
|
||||
See also :ref:`pointervars_programming`
|
||||
|
||||
**LSB/MSB split word arrays:**
|
||||
For (u)word arrays, you can make the compiler layout the array in memory as two separate arrays,
|
||||
one with the LSBs and one with the MSBs of the word values. This is more efficient when storing
|
||||
and reading words from the array (the index can be used twice).
|
||||
Add the ``@split`` tag to the variable declaration to do this.
|
||||
In the assembly code, the array will be generated as two byte arrays namely ``name_lsb`` and ``name_msb``.
|
||||
Note that the maximum length of a split word array is 256! (regular word arrays are limited to 128 elements).
|
||||
one with the LSBs and one with the MSBs of the word values. This makes it more efficient to access
|
||||
values from the array (smaller and faster code). It also doubles the maximum size of the array from 128 words to 256 words!
|
||||
The ``@split`` tag should be added to the variable declaration to do this.
|
||||
In the assembly code, the array will then be generated as two byte arrays namely ``name_lsb`` and ``name_msb``.
|
||||
|
||||
.. caution::
|
||||
Not all array operations are supported yet on "split word arrays".
|
||||
The compiler may give an unpleasant error or crash when you hit such a case in your code.
|
||||
If this happens simply revert to a regular word array and please report the issue,
|
||||
If you get an error message, simply revert to a regular word array and please report the issue,
|
||||
so that more support can be added in the future where it is needed.
|
||||
|
||||
|
||||
@@ -798,224 +797,8 @@ Otherwise the compiler will issue a warning about discarding a result value.
|
||||
then undefined because the variables will get overwritten.
|
||||
|
||||
|
||||
.. _builtinfunctions:
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
|
||||
There's a set of predefined functions in the language. These are fixed and can't be redefined in user code.
|
||||
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
|
||||
|
||||
|
||||
Math
|
||||
^^^^
|
||||
|
||||
abs (x)
|
||||
Returns the absolute value of a number (integer or floating point).
|
||||
|
||||
min (x, y)
|
||||
Returns the smallest of x and y. Supported for integer types only, for floats use ``floats.minf()`` instead.
|
||||
|
||||
max (x, y)
|
||||
Returns the largest of x and y. Supported for integer types only, for floats use ``floats.maxf()`` instead.
|
||||
|
||||
clamp (value, minimum, maximum)
|
||||
Returns the value restricted to the given minimum and maximum.
|
||||
Supported for integer types only, for floats use ``floats.clampf()`` instead.
|
||||
|
||||
sgn (x)
|
||||
Get the sign of the value (integer or floating point).
|
||||
The result is a byte: -1, 0 or 1 (negative, zero, positive).
|
||||
|
||||
sqrt (w)
|
||||
Returns the square root of the number.
|
||||
Supports unsigned integer (result is ubyte) and floating point numbers.
|
||||
To do the reverse - squaring a number - just write ``x*x``.
|
||||
|
||||
divmod (dividend, divisor, quotient, remainder)
|
||||
Performs division only once and returns both quotient and remainder in a single call, where using '/' and '%' separately
|
||||
would perform the division operation twice.
|
||||
All values are ubytes or all are uwords.
|
||||
The last two arguments must be variables to receive the quotient and remainder results, respectively.
|
||||
|
||||
|
||||
Array operations
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
any (x)
|
||||
true if any of the values in the array value x is 'true' (not zero), else false.
|
||||
|
||||
all (x)
|
||||
true if all of the values in the array value x are 'true' (not zero), else false.
|
||||
|
||||
len (x)
|
||||
Number of values in the array value x, or the number of characters in a string (excluding the 0-byte).
|
||||
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
|
||||
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
|
||||
length of the string during execution, the value of len(s) may no longer be correct!
|
||||
(use the ``string.length`` routine if you want to dynamically determine the length by counting to the
|
||||
first 0-byte)
|
||||
|
||||
reverse (array)
|
||||
Reverse the values in the array (in-place).
|
||||
Can be used after sort() to sort an array in descending order.
|
||||
|
||||
sort (array)
|
||||
Sort the array in ascending order (in-place)
|
||||
Supported are arrays of bytes or word values.
|
||||
Sorting a floating-point array is not supported right now, as a general sorting routine for this will
|
||||
be extremely slow. Either build one yourself or find another solution that doesn't require sorting.
|
||||
Finally, note that sorting an array with strings in it will not do what you might think;
|
||||
it considers the array as just an array of integer words and sorts the string *pointers* accordingly.
|
||||
Sorting strings alphabetically has to be programmed yourself if you need it.
|
||||
|
||||
|
||||
Miscellaneous
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
cmp (x,y)
|
||||
Compare the integer value x to integer value y. Doesn't return a value or boolean result, only sets the processor's status bits!
|
||||
You can use a conditional jumps (``if_cc`` etcetera) to act on this.
|
||||
Normally you should just use a comparison expression (``x < y``)
|
||||
|
||||
lsb (x)
|
||||
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
|
||||
|
||||
msb (x)
|
||||
Get the most significant byte of the word x.
|
||||
|
||||
mkword (msb, lsb)
|
||||
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
|
||||
So mkword($80, $22) results in $8022.
|
||||
|
||||
.. note::
|
||||
The arguments to the mkword() function are in 'natural' order that is first the msb then the lsb.
|
||||
Don't get confused by how the system actually stores this 16-bit word value in memory (which is
|
||||
in little-endian format, so lsb first then msb)
|
||||
|
||||
peek (address)
|
||||
same as @(address) - reads the byte at the given address in memory.
|
||||
|
||||
peekw (address)
|
||||
reads the word value at the given address in memory. Word is read as usual little-endian lsb/msb byte order.
|
||||
|
||||
peekf (address)
|
||||
reads the float value at the given address in memory. On CBM machines, this reads 5 bytes.
|
||||
|
||||
poke (address, value)
|
||||
same as @(address)=value - writes the byte value at the given address in memory.
|
||||
|
||||
pokew (address, value)
|
||||
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
|
||||
|
||||
pokef (address, value)
|
||||
writes the float value at the given address in memory. On CBM machines, this writes 5 bytes.
|
||||
|
||||
pokemon (address, value)
|
||||
Like poke(), but also returns the previous value in the given address.
|
||||
Also doesn't have anything to do with a certain video game.
|
||||
|
||||
rol (x)
|
||||
Rotate the bits in x (byte or word) one position to the left.
|
||||
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
|
||||
while the highest bit will become the new Carry flag value.
|
||||
(essentially, it is a 9-bit or 17-bit rotation)
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can rol a memory location directly by using the direct memory access syntax, so like ``rol(@($5000))``
|
||||
|
||||
rol2 (x)
|
||||
Like ``rol`` but now as 8-bit or 16-bit rotation.
|
||||
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can rol a memory location directly by using the direct memory access syntax, so like ``rol2(@($5000))``
|
||||
|
||||
ror (x)
|
||||
Rotate the bits in x (byte or word) one position to the right.
|
||||
This uses the CPU's rotate semantics: the highest bit will be set to the current value of the Carry flag,
|
||||
while bit 0 will become the new Carry flag value.
|
||||
(essentially, it is a 9-bit or 17-bit rotation)
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can ror a memory location directly by using the direct memory access syntax, so like ``ror(@($5000))``
|
||||
|
||||
ror2 (x)
|
||||
Like ``ror`` but now as 8-bit or 16-bit rotation.
|
||||
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can ror a memory location directly by using the direct memory access syntax, so like ``ror2(@($5000))``
|
||||
|
||||
setlsb (x, value)
|
||||
Sets the least significant byte of word variable x to a new value. Leaves the MSB untouched.
|
||||
|
||||
setmsb (x, value)
|
||||
Sets the most significant byte of word variable x to a new value. Leaves the LSB untouched.
|
||||
|
||||
sizeof (name) ; sizeof (number)
|
||||
Number of bytes that the object 'name', or the number 'number' occupies in memory.
|
||||
This is a constant determined by the data type of
|
||||
the object. For instance, for a variable of type uword, the sizeof is 2.
|
||||
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
|
||||
Note: usually you will be interested in the number of elements in an array, use len() for that.
|
||||
|
||||
memory (name, size, alignment)
|
||||
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
|
||||
with the given name. The block is uninitialized memory, it is *not* set to zero!
|
||||
If you specify an alignment value >1, it means the block of memory will
|
||||
be aligned to such a dividable address in memory, for instance an alignment of $100 means the
|
||||
memory block is aligned on a page boundary, and $2 means word aligned (even addresses).
|
||||
Requesting the address of such a named memory block again later with
|
||||
the same name, will result in the same address as before.
|
||||
When reusing blocks in that way, it is required that the size argument is the same,
|
||||
otherwise you'll get a compilation error.
|
||||
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
|
||||
not suffice; for instance if you need more than 256 consecutive bytes.
|
||||
The return value is just a simple uword address so it cannot be used as an array in your program.
|
||||
You can only treat it as a pointer or use it in inline assembly.
|
||||
|
||||
call (address) -> uword
|
||||
Calls a subroutine given by its memory address. You cannot pass arguments directly,
|
||||
although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example.
|
||||
It is assumed the subroutine returns a word value (in AY), if it does not, just add void to the call to ignore the result value.
|
||||
This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable.
|
||||
But because it doesn't handle bank switching etcetera by itself,
|
||||
it is a lot faster than ``callfar``. And it works on other systems than just the Commander X16.
|
||||
|
||||
callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now
|
||||
Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine)
|
||||
Be aware that ram OR rom bank may be changed depending on the address it jumps to!
|
||||
The argumentword will be loaded into the A+Y registers before calling the routine.
|
||||
The uword value that the routine returns in the A+Y registers, will be returned.
|
||||
NOTE: this routine is very inefficient, so don't use it to call often. Set the bank yourself
|
||||
or even write a custom tailored trampoline routine if you need to. Or use ``call`` if you can.
|
||||
|
||||
syscall (callnr), syscall1 (callnr, arg), syscall2 (callnr, arg1, arg2), syscall3 (callnr, arg1, arg2, arg3)
|
||||
Functions for doing a system call on targets that support this. Currently no actual target
|
||||
uses this though except, possibly, the experimental code generation target!
|
||||
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
|
||||
specific memory locations. So these builtin function calls are not useful yet except for
|
||||
experimentation in new code generation targets.
|
||||
|
||||
rsave
|
||||
Saves all registers including status (or only X) on the stack
|
||||
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved,
|
||||
but you can use ``cx16.save_virtual_registers()`` for that.
|
||||
|
||||
rrestore
|
||||
Restore all registers including status (or only X) back from the cpu hardware stack
|
||||
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* restored,
|
||||
but you can use ``cx16.restore_virtual_registers()`` for that.
|
||||
|
||||
|
||||
Library routines
|
||||
----------------
|
||||
|
||||
There are many routines available in the compiler libraries.
|
||||
Some are used internally by the compiler as well.
|
||||
Library routines and builtin functions
|
||||
--------------------------------------
|
||||
|
||||
There are many routines available in the compiler libraries or as builtin functions.
|
||||
The most important ones can be found in the :doc:`libraries` chapter.
|
||||
|
||||
There's too many to list here, just have a look through the source code
|
||||
of the library modules to see what's there.
|
||||
(They can be found in the compiler/res directory)
|
||||
The example programs also use a small set of the library routines, you can study
|
||||
their source code to see how they might be used.
|
||||
@@ -344,7 +344,7 @@ Tag Effect
|
||||
@requirezp force the variable into Zero page. If ZP is full, compilation will fail.
|
||||
@nozp force the variable to normal system ram, never place it into zeropage.
|
||||
@shared means the variable is shared with some assembly code and that it cannot be optimized away if not used elsewhere.
|
||||
@split (only valid on (u)word arrays) Makes the array to be placed in memory as 2 separate byte arrays; one with the LSBs one with the MSBs of the word values. May improve performance.
|
||||
@split (only valid on (u)word arrays) Makes the array to be placed in memory as 2 separate byte arrays; one with the LSBs one with the MSBs of the word values. Usually improves performance and code size.
|
||||
========== ======
|
||||
|
||||
|
||||
@@ -390,8 +390,8 @@ type identifier type storage size example var declara
|
||||
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar``
|
||||
``word[x]`` signed word array 2*x bytes ``word[4] myvar``
|
||||
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar``
|
||||
``float[x]`` floating-point array 5*x bytes ``float[4] myvar``
|
||||
``bool[x]`` boolean array 5*x bytes ``bool[4] myvar`` note: consider using bit flags in a byte or word instead to save space
|
||||
``float[x]`` floating-point array 5*x bytes ``float[4] myvar``. The 5 bytes per float is on CBM targets.
|
||||
``bool[x]`` boolean array x bytes ``bool[4] myvar`` note: consider using bit flags in a byte or word instead to save space
|
||||
``byte[]`` signed byte array depends on value ``byte[] myvar = [1, 2, 3, 4]``
|
||||
``ubyte[]`` unsigned byte array depends on value ``ubyte[] myvar = [1, 2, 3, 4]``
|
||||
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
|
||||
@@ -525,7 +525,8 @@ Array indexing
|
||||
|
||||
Strings and arrays are a sequence of values. You can access the individual values by indexing.
|
||||
Negative index means counted from the end of the array rather than the beginning, where -1 means
|
||||
the last element in the array, -2 the second-to-last, etc. (Python uses this same scheme)
|
||||
the last element in the array, -2 the second-to-last, etc. (Python uses this same scheme.
|
||||
Note that this syntax is only valid for arrays, not for strings! Python does allow the latter, but prog8 does not right now.)
|
||||
Use brackets to index into an array: ``arrayvar[x]`` ::
|
||||
|
||||
array[2] ; the third byte in the array (index is 0-based)
|
||||
@@ -547,13 +548,14 @@ String length is limited to 255 characters.
|
||||
Here are examples of the various encodings:
|
||||
|
||||
- ``"hello"`` a string translated into the default character encoding (PETSCII on the CBM machines)
|
||||
- ``petscii:"hello"`` string in CBM PETSCII encoding
|
||||
- ``sc:"my name is Alice"`` string in CBM screencode encoding
|
||||
- ``iso:"Ich heiße François"`` string in iso-8859-15 encoding (Latin)
|
||||
- ``iso5:"Хозяин и Работник"`` string in iso-8859-5 encoding (Cyrillic)
|
||||
- ``iso16:"zażółć gęślą jaźń"`` string in iso-8859-16 encoding (Eastern Europe)
|
||||
- ``atascii:"I am Atari!"`` string in "atascii" encoding (Atari 8-bit)
|
||||
- ``cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"`` string in "cp437" encoding (IBM PC codepage 437)
|
||||
- ``petscii:"hello"`` string in CBM PETSCII encoding
|
||||
- ``sc:"my name is Alice"`` string in CBM screencode encoding
|
||||
- ``iso:"Ich heiße François"`` string in iso-8859-15 encoding (Latin)
|
||||
- ``iso5:"Хозяин и Работник"`` string in iso-8859-5 encoding (Cyrillic)
|
||||
- ``iso16:"zażółć gęślą jaźń"`` string in iso-8859-16 encoding (Eastern Europe)
|
||||
- ``atascii:"I am Atari!"`` string in "atascii" encoding (Atari 8-bit)
|
||||
- ``cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"`` string in "cp437" encoding (IBM PC codepage 437)
|
||||
- ``kata:"アノ ニホンジン ワ ガイコクジン。 # が # ガ"`` string in "kata" encoding (Katakana)
|
||||
|
||||
|
||||
There are several escape sequences available to put special characters into your string value:
|
||||
@@ -974,7 +976,7 @@ where <statements> can be just a single statement or a block again::
|
||||
if_XX {
|
||||
<statements>
|
||||
} else {
|
||||
<alternative statements>
|
||||
<alternative statements>
|
||||
}
|
||||
|
||||
The XX corresponds to one of the processor's branching instructions, so the possibilities are:
|
||||
|
||||
@@ -224,6 +224,8 @@ Here they are, all available in ``cx16``:
|
||||
|
||||
``set_aflow_irq_handler (uword address)``
|
||||
Sets the audio buffer underrun interrupt handler routine. Also enables AFLOW interrupts.
|
||||
Note: the handler must fill the Vera's audio fifo buffer by itself with at least 25% worth of data (1 kb)
|
||||
otherwise the aflow irq keeps triggering.
|
||||
|
||||
``disable_irq_handlers ()``
|
||||
Hand control back to the system default IRQ handler.
|
||||
@@ -1,9 +1,10 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
https://github.com/irmen/prog8/issues/136 (string.find register order issue)
|
||||
|
||||
...
|
||||
Improve register load order in subroutine call args assignments:
|
||||
in certain situations, the "wrong" order of evaluation of function call arguments is done which results
|
||||
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)
|
||||
Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters().
|
||||
|
||||
|
||||
Future Things and Ideas
|
||||
@@ -13,6 +14,7 @@ Compiler:
|
||||
- Relax newline / bracket in parser so that you can put open and close brackets on the same line or on the next line if you so wish. For example be able to write a true one liner?
|
||||
- Can we support signed % (remainder) somehow?
|
||||
- IR: implement missing operators in AssignmentGen (array shifts etc)
|
||||
- expand the kata encoding to somehow translate normal katana to half-widths? (see comment in KatakanaEncoding)
|
||||
- instead of copy-pasting inline asmsubs, make them into a 64tass macro and use that instead.
|
||||
that will allow them to be reused from custom user written assembly code as well.
|
||||
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays.
|
||||
@@ -40,15 +42,14 @@ Compiler:
|
||||
- ir: the @split arrays are currently also split in _lsb/_msb arrays in the IR, and operations take multiple (byte) instructions that may lead to verbose and slow operation and machine code generation down the line.
|
||||
maybe another representation is needed once actual codegeneration is done from the IR...?
|
||||
- ir: getting it in shape for code generation...
|
||||
- ir: make sure that a 6502 codegen based off the IR, still generates BIT instructions when testing bit 7 or 6 of a byte var.
|
||||
- [problematic due to using 64tass:] better support for building library programs, where unused .proc are NOT deleted from the assembly.
|
||||
Perhaps replace all uses of .proc/.pend/.endproc by .block/.bend will fix that with a compiler flag?
|
||||
But all library code written in asm uses .proc already..... (textual search/replace when writing the actual asm?)
|
||||
Once new codegen is written that is based on the IR, this point is mostly moot anyway as that will have its own dead code removal on the IR level.
|
||||
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions
|
||||
- Zig-like defer to execute a statement/anonymousscope at subroutine exit?
|
||||
- Zig-like defer to execute a statement/anonymousscope when subroutine exits? (problem is, we have jump instructions and inline asm , where we lose track of when exactly the subroutine exits...)
|
||||
- generate WASM to eventually run prog8 on a browser canvas? Use binaryen toolkit and/or my binaryen kotlin library?
|
||||
- implement split words arrays all()
|
||||
- implement split words arrays sort()
|
||||
|
||||
|
||||
Libraries:
|
||||
|
||||
Loaded 100 of 130 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user