mirror of
https://github.com/irmen/prog8.git
synced 2025-06-14 11:23:37 +00:00
Compare commits
120 Commits
Author | SHA1 | Date | |
---|---|---|---|
1f346230e3 | |||
a2860a7c8c | |||
df997e5d3b | |||
a67a82c921 | |||
ea0fe8d3d2 | |||
2560042ac7 | |||
3d1d0696b9 | |||
83f893f50b | |||
9ecf95b075 | |||
7748c261da | |||
a2db44f80c | |||
b438d8aec0 | |||
4ac169b210 | |||
56dc6d7f1e | |||
45b8762188 | |||
cafab98d10 | |||
9256f910f0 | |||
32068a832a | |||
47c2c0376a | |||
f0dadc4a43 | |||
960b60cd2d | |||
d6abd72e55 | |||
0a568f2530 | |||
c52aa648c0 | |||
3d23b39f4c | |||
f3a4048ebf | |||
1b07637cc4 | |||
68b75fd558 | |||
7c5ec1853d | |||
e8f4686430 | |||
02348924d0 | |||
69dcb4dbda | |||
c838821615 | |||
8b4ac7801f | |||
64a411628d | |||
e8e25c6fd6 | |||
62485b6851 | |||
54025d2bf5 | |||
f5ebf79e71 | |||
66d5490702 | |||
42fe052f9f | |||
58d9c46a9b | |||
e4648e2138 | |||
110e047681 | |||
17d403d812 | |||
0a53bd4956 | |||
e52d05c7db | |||
b00db4f8a2 | |||
0c2f30fd45 | |||
e08871c637 | |||
ff715881bc | |||
0e2e5ffa52 | |||
8095c4c155 | |||
e86246a985 | |||
625aaa02eb | |||
787e35c9f3 | |||
8887e6af91 | |||
dde4c751da | |||
3c39baf1d6 | |||
b292124f3c | |||
c0035ba1a2 | |||
2491509c6a | |||
107935ed31 | |||
31491c62c5 | |||
eacf8b896a | |||
7936fc5bd8 | |||
adfaddbcf4 | |||
74db5c6be7 | |||
f9399bcce7 | |||
87600b23db | |||
cedfb17b18 | |||
fa4c83df6b | |||
42c8720e8b | |||
b334d89715 | |||
4f5d36a84d | |||
8f379e2262 | |||
fa11a6e18b | |||
52bedce8f4 | |||
4c82af36e6 | |||
dafa0d9138 | |||
2e0450d7ed | |||
6af3209d4d | |||
5d362047e2 | |||
f48d6ca9f8 | |||
964e8e0a17 | |||
1f60a2d8b9 | |||
5fd83f2757 | |||
c80df4140b | |||
53e1729e2f | |||
ab2d1122a9 | |||
5190594c8a | |||
c858ceeb58 | |||
f0f52b9166 | |||
00c6f74481 | |||
2177ba0ed2 | |||
3483515346 | |||
75a06d2a40 | |||
53ac11983b | |||
69f4a4d4f8 | |||
222bcb808f | |||
686483f51a | |||
8df3da11e3 | |||
84dafda0e4 | |||
b909facfe5 | |||
7780d94de1 | |||
f2c440e466 | |||
4937e004b5 | |||
4cb383dccb | |||
c8a4b6f23c | |||
857724c7e6 | |||
a9b0400d13 | |||
2d1e5bbc7e | |||
60627ce756 | |||
7961a09d16 | |||
613efcacc7 | |||
7e8db16e18 | |||
1fbbed7e23 | |||
984272beb4 | |||
b9ce94bb68 | |||
f4c4ee78d9 |
22
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
Normal file
22
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
Normal file
@ -0,0 +1,22 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:4.6.3" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
24
.idea/libraries/io_kotest_property_jvm.xml
generated
Normal file
24
.idea/libraries/io_kotest_property_jvm.xml
generated
Normal file
@ -0,0 +1,24 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.property.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-property-jvm:4.6.3" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/4.6.3/kotest-property-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/mifmif/generex/1.0.2/generex-1.0.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/dk/brics/automaton/automaton/1.11-8/automaton-1.11-8.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
56
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
Normal file
56
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
Normal file
@ -0,0 +1,56 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:4.6.3" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/4.6.3/kotest-runner-junit5-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/4.6.3/kotest-framework-api-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/4.6.3/kotest-framework-engine-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.105/classgraph-4.8.105.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-util/1.5.0/kotlin-script-util-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/trove4j/1.0.20181211/trove4j-1.0.20181211.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-daemon-client/1.5.0/kotlin-daemon-client-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.3.8/kotlinx-coroutines-core-1.3.8.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-jvm/1.5.0/kotlin-scripting-jvm-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-common/1.5.0/kotlin-scripting-common-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/4.6.3/kotest-framework-discovery-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/4.6.3/kotest-extensions-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.6/commons-io-2.6.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.9.3/mockk-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.9.3/mockk-common-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.9.3/mockk-dsl-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.9.3/mockk-dsl-jvm-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.9.3/mockk-agent-jvm-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.9.3/mockk-agent-api-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.9.3/mockk-agent-common-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.0.1/objenesis-3.0.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.9.10/byte-buddy-1.9.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.9.10/byte-buddy-agent-1.9.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/4.6.3/kotest-framework-concurrency-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.2/junit-platform-engine-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.2/junit-platform-commons-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.6.2/junit-platform-suite-api-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.6.2/junit-platform-launcher-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.2/junit-jupiter-api-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-runtime/1.5.0/kotlin-script-runtime-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
5
.idea/misc.xml
generated
5
.idea/misc.xml
generated
@ -16,7 +16,10 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
@ -3,6 +3,7 @@ plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id "io.kotest" version "0.3.8"
|
||||
}
|
||||
|
||||
java {
|
||||
@ -18,11 +19,7 @@ dependencies {
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -43,7 +40,6 @@ sourceSets {
|
||||
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
|
@ -13,7 +13,7 @@
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -1,35 +1,41 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object C64Target: ICompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,12 @@ import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
@ -13,23 +18,28 @@ import prog8.compilerinterface.ICompilationTarget
|
||||
object Cx16Target: ICompilationTarget {
|
||||
override val name = "cx16"
|
||||
override val machine = CX16MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,12 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0xc000
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801u
|
||||
override val RAW_LOAD_ADDRESS = 0xc000u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
||||
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
@ -56,7 +56,7 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
@ -76,10 +76,10 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
|
||||
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x03u // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fb+$fc
|
||||
|
||||
|
||||
init {
|
||||
@ -87,9 +87,9 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
free.addAll(0x04..0xf9)
|
||||
free.add(0xff)
|
||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||
free.addAll(0x04u..0xf9u)
|
||||
free.add(0xffu)
|
||||
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(
|
||||
@ -106,7 +106,7 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
// 0x90-0xfa is 'kernal work storage area'
|
||||
))
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
@ -118,7 +118,7 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
))
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||
@ -126,7 +126,7 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
free.clear()
|
||||
@ -137,11 +137,11 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short):
|
||||
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte):
|
||||
IMachineFloat {
|
||||
|
||||
companion object {
|
||||
val zero = Mflpt5(0, 0, 0, 0, 0)
|
||||
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
@ -175,11 +175,11 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
Mflpt5(
|
||||
exponent.toShort(),
|
||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
|
||||
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
|
||||
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
|
||||
(mantLong.and(0x000000ffL)).toShort())
|
||||
exponent.toUByte(),
|
||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
|
||||
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
|
||||
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
|
||||
(mantLong.and(0x000000ffL)).toUByte())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -187,7 +187,7 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun toDouble(): Double {
|
||||
if (this == zero) return 0.0
|
||||
val exp = b0 - 128
|
||||
val exp = b0.toInt() - 128
|
||||
val sign = (b1.toInt() and 0x80) > 0
|
||||
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||
|
@ -1065,15 +1065,15 @@ object Petscii {
|
||||
else -> chr
|
||||
}
|
||||
|
||||
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
|
||||
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
|
||||
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
|
||||
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
|
||||
val chr = replaceSpecial(chr3)
|
||||
val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr]
|
||||
return screencode?.toShort() ?: when (chr) {
|
||||
'\u0000' -> 0.toShort()
|
||||
return screencode?.toUByte() ?: when (chr) {
|
||||
'\u0000' -> 0u
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(chr.code - 0x8000).toShort()
|
||||
(chr.code - 0x8000).toUByte()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
@ -1095,7 +1095,7 @@ object Petscii {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String {
|
||||
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||
return petscii.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingPetsciiLowercase.size)
|
||||
@ -1104,15 +1104,15 @@ object Petscii {
|
||||
}.joinToString("")
|
||||
}
|
||||
|
||||
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
|
||||
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
|
||||
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
|
||||
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
|
||||
val chr = replaceSpecial(chr3)
|
||||
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
|
||||
return screencode?.toShort() ?: when (chr) {
|
||||
'\u0000' -> 0.toShort()
|
||||
return screencode?.toUByte() ?: when (chr) {
|
||||
'\u0000' -> 0u
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(chr.code - 0x8000).toShort()
|
||||
(chr.code - 0x8000).toUByte()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
@ -1134,7 +1134,7 @@ object Petscii {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String {
|
||||
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||
return screencode.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingScreencodeLowercase.size)
|
||||
@ -1143,38 +1143,37 @@ object Petscii {
|
||||
}.joinToString("")
|
||||
}
|
||||
|
||||
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Result<Short, CharConversionException> {
|
||||
val code = when {
|
||||
petscii_code < 0 -> return Err(CharConversionException("petscii code out of range"))
|
||||
petscii_code <= 0x1f -> petscii_code + 128
|
||||
petscii_code <= 0x3f -> petscii_code.toInt()
|
||||
petscii_code <= 0x5f -> petscii_code - 64
|
||||
petscii_code <= 0x7f -> petscii_code - 32
|
||||
petscii_code <= 0x9f -> petscii_code + 64
|
||||
petscii_code <= 0xbf -> petscii_code - 64
|
||||
petscii_code <= 0xfe -> petscii_code - 128
|
||||
petscii_code == 255.toShort() -> 95
|
||||
fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> {
|
||||
val code: UInt = when {
|
||||
petscii_code <= 0x1fu -> petscii_code + 128u
|
||||
petscii_code <= 0x3fu -> petscii_code.toUInt()
|
||||
petscii_code <= 0x5fu -> petscii_code - 64u
|
||||
petscii_code <= 0x7fu -> petscii_code - 32u
|
||||
petscii_code <= 0x9fu -> petscii_code + 64u
|
||||
petscii_code <= 0xbfu -> petscii_code - 64u
|
||||
petscii_code <= 0xfeu -> petscii_code - 128u
|
||||
petscii_code == 255.toUByte() -> 95u
|
||||
else -> return Err(CharConversionException("petscii code out of range"))
|
||||
}
|
||||
if(inverseVideo)
|
||||
return Ok((code or 0x80).toShort())
|
||||
return Ok(code.toShort())
|
||||
if(inverseVideo) {
|
||||
return Ok((code or 0x80u).toUByte())
|
||||
}
|
||||
return Ok(code.toUByte())
|
||||
}
|
||||
|
||||
fun scr2petscii(screencode: Short): Result<Short, CharConversionException> {
|
||||
val petscii = when {
|
||||
screencode < 0 -> return Err(CharConversionException("screencode out of range"))
|
||||
screencode <= 0x1f -> screencode + 64
|
||||
screencode <= 0x3f -> screencode.toInt()
|
||||
screencode <= 0x5d -> screencode +123
|
||||
screencode == 0x5e.toShort() -> 255
|
||||
screencode == 0x5f.toShort() -> 223
|
||||
screencode <= 0x7f -> screencode + 64
|
||||
screencode <= 0xbf -> screencode - 128
|
||||
screencode <= 0xfe -> screencode - 64
|
||||
screencode == 255.toShort() -> 191
|
||||
fun scr2petscii(screencode: UByte): Result<UByte, CharConversionException> {
|
||||
val petscii: UInt = when {
|
||||
screencode <= 0x1fu -> screencode + 64u
|
||||
screencode <= 0x3fu -> screencode.toUInt()
|
||||
screencode <= 0x5du -> screencode +123u
|
||||
screencode == 0x5e.toUByte() -> 255u
|
||||
screencode == 0x5f.toUByte() -> 223u
|
||||
screencode <= 0x7fu -> screencode + 64u
|
||||
screencode <= 0xbfu -> screencode - 128u
|
||||
screencode <= 0xfeu -> screencode - 64u
|
||||
screencode == 255.toUByte() -> 191u
|
||||
else -> return Err(CharConversionException("screencode out of range"))
|
||||
}
|
||||
return Ok(petscii.toShort())
|
||||
return Ok(petscii.toUByte())
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,16 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
|
||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compilerinterface.IMachineDefinition
|
||||
|
||||
|
||||
fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
||||
|
||||
|
||||
fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
|
||||
|
||||
var numberOfOptimizations = 0
|
||||
|
||||
@ -31,7 +37,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeStoreLoadSame(linesByFour)
|
||||
mods = optimizeStoreLoadSame(linesByFour, machine, program)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
@ -46,7 +52,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||
}
|
||||
|
||||
var linesByFourteen = getLinesBy(lines, 14)
|
||||
mods = optimizeSameAssignments(linesByFourteen)
|
||||
mods = optimizeSameAssignments(linesByFourteen, machine, program)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFourteen = getLinesBy(lines, 14)
|
||||
@ -111,22 +117,22 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||
|
||||
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
||||
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
|
||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
|
||||
// The better place to do this is in the Compiler instead and never create these types of assembly, but hey
|
||||
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFourteen) {
|
||||
val first = pair[0].value.trimStart()
|
||||
val second = pair[1].value.trimStart()
|
||||
val third = pair[2].value.trimStart()
|
||||
val fourth = pair[3].value.trimStart()
|
||||
val fifth = pair[4].value.trimStart()
|
||||
val sixth = pair[5].value.trimStart()
|
||||
val seventh = pair[6].value.trimStart()
|
||||
val eighth = pair[7].value.trimStart()
|
||||
for (lines in linesByFourteen) {
|
||||
val first = lines[0].value.trimStart()
|
||||
val second = lines[1].value.trimStart()
|
||||
val third = lines[2].value.trimStart()
|
||||
val fourth = lines[3].value.trimStart()
|
||||
val fifth = lines[4].value.trimStart()
|
||||
val sixth = lines[5].value.trimStart()
|
||||
val seventh = lines[6].value.trimStart()
|
||||
val eighth = lines[7].value.trimStart()
|
||||
|
||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("sta") && eighth.startsWith("sty")) {
|
||||
@ -135,9 +141,13 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val thirdvalue = fifth.substring(4)
|
||||
val fourthvalue = sixth.substring(4)
|
||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||
mods.add(Modification(pair[4].index, true, null))
|
||||
mods.add(Modification(pair[5].index, true, null))
|
||||
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||
val address1 = getAddressArg(first, program)
|
||||
val address2 = getAddressArg(second, program)
|
||||
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
|
||||
mods.add(Modification(lines[4].index, true, null))
|
||||
mods.add(Modification(lines[5].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,8 +155,10 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = third.substring(4)
|
||||
if(firstvalue==secondvalue) {
|
||||
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
|
||||
mods.add(Modification(pair[2].index, true, null))
|
||||
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,12 +166,12 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
||||
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
val nineth = pair[8].value.trimStart()
|
||||
val tenth = pair[9].value.trimStart()
|
||||
val eleventh = pair[10].value.trimStart()
|
||||
val twelveth = pair[11].value.trimStart()
|
||||
val thirteenth = pair[12].value.trimStart()
|
||||
val fourteenth = pair[13].value.trimStart()
|
||||
val nineth = lines[8].value.trimStart()
|
||||
val tenth = lines[9].value.trimStart()
|
||||
val eleventh = lines[10].value.trimStart()
|
||||
val twelveth = lines[11].value.trimStart()
|
||||
val thirteenth = lines[12].value.trimStart()
|
||||
val fourteenth = lines[13].value.trimStart()
|
||||
|
||||
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
||||
@ -167,24 +179,147 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
|
||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||
// identical float init
|
||||
mods.add(Modification(pair[7].index, true, null))
|
||||
mods.add(Modification(pair[8].index, true, null))
|
||||
mods.add(Modification(pair[9].index, true, null))
|
||||
mods.add(Modification(pair[10].index, true, null))
|
||||
mods.add(Modification(lines[7].index, true, null))
|
||||
mods.add(Modification(lines[8].index, true, null))
|
||||
mods.add(Modification(lines[9].index, true, null))
|
||||
mods.add(Modification(lines[10].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var overlappingMods = false
|
||||
/*
|
||||
sta prog8_lib.retval_intermX ; remove
|
||||
sty prog8_lib.retval_intermY ; remove
|
||||
lda prog8_lib.retval_intermX ; remove
|
||||
ldy prog8_lib.retval_intermY ; remove
|
||||
sta A1
|
||||
sty A2
|
||||
*/
|
||||
if(first.startsWith("st") && second.startsWith("st")
|
||||
&& third.startsWith("ld") && fourth.startsWith("ld")
|
||||
&& fifth.startsWith("st") && sixth.startsWith("st")) {
|
||||
val reg1 = first[2]
|
||||
val reg2 = second[2]
|
||||
val reg3 = third[2]
|
||||
val reg4 = fourth[2]
|
||||
val reg5 = fifth[2]
|
||||
val reg6 = sixth[2]
|
||||
if (reg1 == reg3 && reg1 == reg5 && reg2 == reg4 && reg2 == reg6) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
val fourthvalue = fourth.substring(4)
|
||||
if(firstvalue.contains("prog8_lib.retval_interm") && secondvalue.contains("prog8_lib.retval_interm")
|
||||
&& firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
overlappingMods = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
sta A1
|
||||
sty A2
|
||||
lda A1 ; can be removed
|
||||
ldy A2 ; can be removed if not followed by a branch instuction
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
|
||||
&& third.startsWith("ld") && fourth.startsWith("ld")) {
|
||||
val reg1 = first[2]
|
||||
val reg2 = second[2]
|
||||
val reg3 = third[2]
|
||||
val reg4 = fourth[2]
|
||||
if(reg1==reg3 && reg2==reg4) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
val fourthvalue = fourth.substring(4)
|
||||
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
if (!fifth.startsWith('b'))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
sta A1
|
||||
sty A2
|
||||
lda A1 ; can be removed if not followed by a branch instruction
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
|
||||
&& third.startsWith("ld") && !fourth.startsWith("b")) {
|
||||
val reg1 = first[2]
|
||||
val reg3 = third[2]
|
||||
if(reg1==reg3) {
|
||||
val firstvalue = first.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
if(firstvalue==thirdvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
sta A1
|
||||
ldy A1 ; make tay
|
||||
sta A1 ; remove
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("sta") && second.startsWith("ld")
|
||||
&& third.startsWith("sta") && second.length>4) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
val reg2 = second[2]
|
||||
mods.add(Modification(lines[1].index, false, " ta$reg2"))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
sta A
|
||||
sta A
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")) {
|
||||
if(first[2]==second[2]) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
if(firstvalue==secondvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM) but how does this code know?
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value.trimStart()
|
||||
val second = pair[1].value.trimStart()
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[1].value.trimStart()
|
||||
val second = lines[2].value.trimStart()
|
||||
|
||||
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx ")) ||
|
||||
@ -196,32 +331,101 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||
) {
|
||||
val third = pair[2].value.trimStart()
|
||||
if(!third.startsWith("b")) {
|
||||
// no branch instruction follows, we can potentiall remove the load instruction
|
||||
val third = lines[3].value.trimStart()
|
||||
val attemptRemove =
|
||||
if(third.startsWith("b")) {
|
||||
// a branch instruction follows, we can only remove the load instruction if
|
||||
// another load instruction of the same register precedes the store instruction
|
||||
// (otherwise wrong cpu flags are used)
|
||||
val loadinstruction = second.substring(0, 3)
|
||||
lines[0].value.trimStart().startsWith(loadinstruction)
|
||||
}
|
||||
else {
|
||||
// no branch instruction follows, we can remove the load instruction
|
||||
val address = getAddressArg(lines[2].value, program)
|
||||
address==null || !machine.isIOAddress(address)
|
||||
}
|
||||
|
||||
if(attemptRemove) {
|
||||
val firstLoc = first.substring(4).trimStart()
|
||||
val secondLoc = second.substring(4).trimStart()
|
||||
if (firstLoc == secondLoc) {
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
}
|
||||
if (firstLoc == secondLoc)
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
}
|
||||
else if(first=="pha" && second=="pla" ||
|
||||
first=="phx" && second=="plx" ||
|
||||
first=="phy" && second=="ply" ||
|
||||
first=="php" && second=="plp") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
} else if(first=="pha" && second=="plx") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " tax"))
|
||||
} else if(first=="pha" && second=="ply") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " tay"))
|
||||
} else if(first=="phx" && second=="pla") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " txa"))
|
||||
} else if(first=="phx" && second=="ply") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " txy"))
|
||||
} else if(first=="phy" && second=="pla") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " tya"))
|
||||
} else if(first=="phy" && second=="plx") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " tyx"))
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
|
||||
|
||||
private fun getAddressArg(line: String, program: Program): UInt? {
|
||||
val loadArg = line.trimStart().substring(3).trim()
|
||||
return when {
|
||||
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
|
||||
loadArg.startsWith('%') -> loadArg.substring(1).toUIntOrNull(2)
|
||||
loadArg.startsWith('#') -> null
|
||||
loadArg.startsWith('(') -> null
|
||||
loadArg[0].isLetter() -> {
|
||||
val identMatch = identifierRegex.find(loadArg)
|
||||
if(identMatch!=null) {
|
||||
val identifier = identMatch.value
|
||||
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
|
||||
if(decl!=null) {
|
||||
when(decl.type){
|
||||
VarDeclType.VAR -> null
|
||||
VarDeclType.CONST,
|
||||
VarDeclType.MEMORY -> (decl.value as NumericLiteralValue).number.toUInt()
|
||||
}
|
||||
}
|
||||
else null
|
||||
} else null
|
||||
}
|
||||
else -> loadArg.substring(1).toUIntOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value
|
||||
val second = pair[1].value
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[0].value
|
||||
val second = lines[1].value
|
||||
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|
||||
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|
||||
|| (" ina" in first || "\tina" in first) && (" dea" in second || "\tdea" in second)
|
||||
|| (" inc a" in first || "\tinc a" in first) && (" dec a" in second || "\tdec a" in second)
|
||||
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
||||
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
|
||||
mods.add(Modification(pair[0].index, true, null))
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)
|
||||
|| (" dea" in first || "\tdea" in first) && (" ina" in second || "\tina" in second)
|
||||
|| (" dec a" in first || "\tdec a" in first) && (" inc a" in second || "\tinc a" in second)) {
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
}
|
||||
}
|
||||
return mods
|
||||
@ -230,12 +434,12 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
|
||||
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// jsr Sub + rts -> jmp Sub
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value
|
||||
val second = pair[1].value
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[0].value
|
||||
val second = lines[1].value
|
||||
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||
mods += Modification(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(pair[1].index, true, null)
|
||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
}
|
||||
return mods
|
||||
|
@ -0,0 +1,59 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.base.Cx16VirtualRegisters
|
||||
import prog8.ast.base.RegisterOrPair
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
|
||||
|
||||
internal fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
|
||||
val order = mutableListOf<Int>()
|
||||
// order is:
|
||||
// 1) cx16 virtual word registers,
|
||||
// 2) paired CPU registers,
|
||||
// 3) single CPU registers (X last), except A,
|
||||
// 4) CPU Carry status flag
|
||||
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
|
||||
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
|
||||
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
|
||||
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters }
|
||||
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A }
|
||||
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null }
|
||||
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
regsWithoutA.forEach {
|
||||
if(it.value.second.registerOrPair != RegisterOrPair.X)
|
||||
order += it.index
|
||||
}
|
||||
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
|
||||
rest.forEach { order += it.index }
|
||||
regA.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
return order
|
||||
}
|
||||
|
||||
internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
paramRegisters: List<RegisterOrStatusflag>): Boolean {
|
||||
fun isClobberRisk(expr: Expression): Boolean {
|
||||
when (expr) {
|
||||
is ArrayIndexedExpression -> {
|
||||
return paramRegisters.any {
|
||||
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
}
|
||||
}
|
||||
is FunctionCall -> {
|
||||
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
|
||||
return isClobberRisk(expr.args[0])
|
||||
if (expr.target.nameInSource == listOf("mkword"))
|
||||
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
|
||||
return !expr.isSimple
|
||||
}
|
||||
else -> return !expr.isSimple
|
||||
}
|
||||
}
|
||||
|
||||
return args.size>1 && args.any { isClobberRisk(it) }
|
||||
}
|
@ -46,7 +46,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sin8", "sin8u", "sin16", "sin16u",
|
||||
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sinr8", "sinr8u", "sinr16", "sinr16u",
|
||||
"cos8", "cos8u", "cos16", "cos16u",
|
||||
"cosr8", "cosr8u", "cosr16", "cosr16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sin", "cos", "tan", "atan",
|
||||
"ln", "log2", "sqrt", "rad",
|
||||
@ -65,6 +67,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||
"pokew" -> funcPokeW(fcall)
|
||||
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
|
||||
"push", "pushw" -> funcPush(fcall, func)
|
||||
"pop", "popw" -> funcPop(fcall, func)
|
||||
"rsave" -> funcRsave()
|
||||
"rsavex" -> funcRsaveX()
|
||||
"rrestore" -> funcRrestore()
|
||||
"rrestorex" -> funcRrestoreX()
|
||||
"cmp" -> funcCmp(fcall)
|
||||
"callfar" -> funcCallFar(fcall)
|
||||
"callrom" -> funcCallRom(fcall)
|
||||
@ -72,6 +80,168 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcRsave() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
phy
|
||||
phx""")
|
||||
else
|
||||
// see http://6502.org/tutorials/register_preservation.html
|
||||
asmgen.out("""
|
||||
php
|
||||
sta P8ZP_SCRATCH_REG
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda P8ZP_SCRATCH_REG""")
|
||||
}
|
||||
|
||||
private fun funcRsaveX() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" phx")
|
||||
else
|
||||
asmgen.out(" txa | pha")
|
||||
}
|
||||
|
||||
private fun funcRrestore() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out("""
|
||||
plx
|
||||
ply
|
||||
pla
|
||||
plp""")
|
||||
else
|
||||
asmgen.out("""
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
plp""")
|
||||
}
|
||||
|
||||
private fun funcRrestoreX() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" plx")
|
||||
else
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
private fun funcPop(fcall: IFunctionCall, func: FSignature) {
|
||||
// note: because A is pushed first so popped last, saving A is often not required here.
|
||||
require(fcall.args[0] is IdentifierReference) {
|
||||
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
|
||||
}
|
||||
val target = (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!
|
||||
val parameter = target.subroutineParameter
|
||||
if(parameter!=null) {
|
||||
val sub = parameter.definingSubroutine!!
|
||||
require(sub.isAsmSubroutine) {
|
||||
"push/pop arg passing only supported on asmsubs ${(fcall as Node).position}"
|
||||
}
|
||||
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
|
||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
||||
if(reg.statusflag!=null) {
|
||||
if(shouldKeepA)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_REG")
|
||||
asmgen.out("""
|
||||
clc
|
||||
pla
|
||||
beq +
|
||||
sec
|
||||
+""")
|
||||
if(shouldKeepA)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_REG")
|
||||
}
|
||||
else {
|
||||
if (func.name == "pop") {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
when (reg.registerOrPair) {
|
||||
RegisterOrPair.A -> asmgen.out(" pla")
|
||||
RegisterOrPair.X -> asmgen.out(" plx")
|
||||
RegisterOrPair.Y -> asmgen.out(" ply")
|
||||
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
|
||||
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
|
||||
}
|
||||
} else {
|
||||
when (reg.registerOrPair) {
|
||||
RegisterOrPair.A -> asmgen.out(" pla")
|
||||
RegisterOrPair.X -> {
|
||||
if(shouldKeepA)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
|
||||
else
|
||||
asmgen.out(" pla | tax")
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
if(shouldKeepA)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
|
||||
else
|
||||
asmgen.out(" pla | tay")
|
||||
}
|
||||
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
|
||||
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// word pop
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
when (reg.registerOrPair) {
|
||||
RegisterOrPair.AX -> asmgen.out(" plx | pla")
|
||||
RegisterOrPair.AY -> asmgen.out(" ply | pla")
|
||||
RegisterOrPair.XY -> asmgen.out(" ply | plx")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = reg.registerOrPair!!.name.lowercase()
|
||||
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
|
||||
}
|
||||
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
|
||||
}
|
||||
else {
|
||||
when (reg.registerOrPair) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pla | tax | pla")
|
||||
RegisterOrPair.AY -> asmgen.out(" pla | tay | pla")
|
||||
RegisterOrPair.XY -> asmgen.out(" pla | tay | pla | tax")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = reg.registerOrPair!!.name.lowercase()
|
||||
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
|
||||
}
|
||||
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, target.datatype, (fcall as Node).definingSubroutine, variableAsmName = asmgen.asmVariableName(target.name))
|
||||
if (func.name == "pop") {
|
||||
asmgen.out(" pla")
|
||||
asmgen.assignRegister(RegisterOrPair.A, tgt)
|
||||
} else {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" ply | pla")
|
||||
else
|
||||
asmgen.out(" pla | tay | pla")
|
||||
asmgen.assignRegister(RegisterOrPair.AY, tgt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPush(fcall: IFunctionCall, func: FSignature) {
|
||||
val signed = fcall.args[0].inferType(program).oneOf(DataType.BYTE, DataType.WORD)
|
||||
if(func.name=="push") {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed)
|
||||
asmgen.out(" pha")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" pha | phy")
|
||||
else
|
||||
asmgen.out(" pha | tya | pha")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: IFunctionCall) {
|
||||
if(asmgen.options.compTarget !is Cx16Target)
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
@ -87,7 +257,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
|
||||
|
||||
val argAddrArg = fcall.args[2]
|
||||
if(argAddrArg.constValue(program)?.number == 0) {
|
||||
if(argAddrArg.constValue(program)?.number == 0.0) {
|
||||
asmgen.out("""
|
||||
jsr cx16.jsrfar
|
||||
.word ${address.toHex()}
|
||||
@ -132,7 +302,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
throw AssemblyError("callrom bank must be <32")
|
||||
|
||||
val argAddrArg = fcall.args[2]
|
||||
if(argAddrArg.constValue(program)?.number == 0) {
|
||||
if(argAddrArg.constValue(program)?.number == 0.0) {
|
||||
asmgen.out("""
|
||||
lda $01
|
||||
pha
|
||||
@ -248,7 +418,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||
val nameRef = fcall.args[0] as IdentifierReference
|
||||
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
|
||||
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
|
||||
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt()
|
||||
|
||||
val existingSize = asmgen.slabs[name]
|
||||
if(existingSize!=null && existingSize!=size)
|
||||
@ -283,11 +453,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
|
||||
else
|
||||
when(func.name) {
|
||||
"sin8", "sin8u", "cos8", "cos8u" -> {
|
||||
"sin8", "sin8u", "sinr8", "sinr8u", "cos8", "cos8u", "cosr8", "cosr8u" -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
}
|
||||
"sin16", "sin16u", "cos16", "cos16u" -> {
|
||||
"sin16", "sin16u", "sinr16", "sinr16u", "cos16", "cos16u", "cosr16", "cosr16u" -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
@ -1229,6 +1399,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("wrong pokew arg type")
|
||||
}
|
||||
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD, null)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -43,7 +43,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
val stepsize=range.step.constValue(program)!!.number.toInt()
|
||||
|
||||
if(stepsize < -1) {
|
||||
val limit = range.to.constValue(program)?.number?.toDouble()
|
||||
val limit = range.to.constValue(program)?.number
|
||||
if(limit==0.0)
|
||||
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
|
||||
}
|
||||
|
@ -5,10 +5,7 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||
@ -21,7 +18,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
|
||||
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
||||
saveXbeforeCall(stmt)
|
||||
translateFunctionCall(stmt)
|
||||
translateFunctionCall(stmt, false)
|
||||
restoreXafterCall(stmt)
|
||||
// just ignore any result values from the function call.
|
||||
}
|
||||
@ -37,6 +34,17 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
|
||||
internal fun saveXbeforeCall(gosub: GoSub) {
|
||||
val sub = gosub.identifier?.targetSubroutine(program)
|
||||
if(sub?.shouldSaveX()==true) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if(regSaveOnStack)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||
else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, gosub.definingSubroutine!!)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(sub.shouldSaveX()) {
|
||||
@ -48,90 +56,76 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||
internal fun restoreXafterCall(gosub: GoSub) {
|
||||
val sub = gosub.identifier?.targetSubroutine(program)
|
||||
if(sub?.shouldSaveX()==true) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if(regSaveOnStack)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||
else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
|
||||
// Output only the code to set up the parameters and perform the actual call
|
||||
// NOTE: does NOT output the code to deal with the result values!
|
||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||
|
||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
val subName = asmgen.asmSymbolName(stmt.target)
|
||||
if(stmt.args.isNotEmpty()) {
|
||||
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
|
||||
val subAsmName = asmgen.asmSymbolName(call.target)
|
||||
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// via variables
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
}
|
||||
if(!isExpression && !sub.isAsmSubroutine)
|
||||
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
|
||||
|
||||
if(sub.isAsmSubroutine) {
|
||||
argumentsViaRegisters(sub, call)
|
||||
if (sub.inline && asmgen.options.optimize) {
|
||||
// inline the subroutine.
|
||||
// we do this by copying the subroutine's statements at the call site.
|
||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||
// (this condition has been enforced by an ast check earlier)
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||
val assembly = sub.statements.single() as InlineAssembly
|
||||
asmgen.translate(assembly)
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
} else {
|
||||
require(sub.isAsmSubroutine)
|
||||
if(sub.parameters.size==1) {
|
||||
// just a single parameter, no risk of clobbering registers
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
|
||||
} else {
|
||||
|
||||
fun isNoClobberRisk(expr: Expression): Boolean {
|
||||
if(expr is AddressOf ||
|
||||
expr is NumericLiteralValue ||
|
||||
expr is StringLiteralValue ||
|
||||
expr is ArrayLiteralValue ||
|
||||
expr is IdentifierReference)
|
||||
return true
|
||||
|
||||
if(expr is FunctionCall) {
|
||||
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
|
||||
return isNoClobberRisk(expr.args[0])
|
||||
if(expr.target.nameInSource==listOf("mkword"))
|
||||
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
when {
|
||||
stmt.args.all {isNoClobberRisk(it)} -> {
|
||||
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
|
||||
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
|
||||
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
|
||||
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
|
||||
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
|
||||
for(arg in cx16virtualRegs)
|
||||
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||
for(arg in cpuRegs)
|
||||
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||
for(arg in statusRegs)
|
||||
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||
}
|
||||
else -> {
|
||||
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
|
||||
registerArgsViaStackEvaluation(stmt, sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
}
|
||||
}
|
||||
|
||||
if(!sub.inline || !asmgen.options.optimize) {
|
||||
asmgen.out(" jsr $subName")
|
||||
} else {
|
||||
// inline the subroutine.
|
||||
// we do this by copying the subroutine's statements at the call site.
|
||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||
// (this condition has been enforced by an ast check earlier)
|
||||
|
||||
// note: for now, this is only reliably supported for asmsubs.
|
||||
if(!sub.isAsmSubroutine)
|
||||
else {
|
||||
if(sub.inline)
|
||||
throw AssemblyError("can only reliably inline asmsub routines at this time")
|
||||
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||
val assembly = sub.statements.single() as InlineAssembly
|
||||
asmgen.translate(assembly)
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
argumentsViaVariables(sub, call)
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
}
|
||||
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
}
|
||||
|
||||
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
|
||||
for(arg in sub.parameters.withIndex().zip(call.args))
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
}
|
||||
|
||||
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
|
||||
} else {
|
||||
if(asmgen.asmsubArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
|
||||
registerArgsViaStackEvaluation(call, sub)
|
||||
} else {
|
||||
asmgen.asmsubArgsEvalOrder(sub).forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
argumentViaRegister(sub, IndexedValue(it, param), arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||
// this is called when one or more of the arguments are 'complex' and
|
||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||
@ -220,11 +214,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
if(argForCarry!=null) {
|
||||
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda P8ESTACK_LO$plusIdxStr,x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ php""") // push the status flags
|
||||
}
|
||||
|
||||
@ -267,7 +260,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + parameter.value.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
|
||||
}
|
||||
|
||||
@ -303,11 +296,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val sourceName = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
pha
|
||||
clc
|
||||
lda $sourceName
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ pla
|
||||
""")
|
||||
}
|
||||
@ -335,8 +327,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val target: AsmAssignTarget =
|
||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
|
||||
else
|
||||
AsmAssignTarget.fromRegisters(register, false, sub, program, asmgen)
|
||||
else {
|
||||
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
||||
AsmAssignTarget.fromRegisters(register, signed, sub, program, asmgen)
|
||||
}
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
if(value is IdentifierReference) {
|
||||
val addr = AddressOf(value, Position.DUMMY)
|
||||
|
@ -39,8 +39,8 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
val origAstTarget: AssignTarget? = null
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
|
||||
val asmVarname: String by lazy {
|
||||
if (array == null)
|
||||
variableAsmName!!
|
||||
@ -56,16 +56,31 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
|
||||
val idt = inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val dt = idt.getOr(DataType.UNDEFINED)
|
||||
when {
|
||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
|
||||
with(assign.target) {
|
||||
val idt = inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val dt = idt.getOr(DataType.UNDEFINED)
|
||||
when {
|
||||
identifier != null -> {
|
||||
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
|
||||
if (parameter!=null) {
|
||||
val sub = parameter.definingSubroutine!!
|
||||
if (sub.isAsmSubroutine) {
|
||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
||||
if(reg.statusflag!=null)
|
||||
throw AssemblyError("can't assign value to processor statusflag directly")
|
||||
else
|
||||
return AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
|
||||
}
|
||||
}
|
||||
return AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
}
|
||||
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,8 +126,8 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val expression: Expression? = null
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
|
||||
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
@ -133,6 +148,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
val parameter = value.targetVarDecl(program)?.subroutineParameter
|
||||
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
|
||||
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
|
||||
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val varName=asmgen.asmVariableName(value)
|
||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||
@ -209,7 +227,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
||||
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
|
||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||
"source storage size must be less or equal to target datatype storage size"
|
||||
"source dt size must be less or equal to target dt size at $position"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,17 +7,14 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CpuType
|
||||
import prog8.compilerinterface.builtinFunctionReturnType
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen,
|
||||
private val exprAsmgen: ExpressionsAsmGen
|
||||
) {
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
|
||||
|
||||
fun translate(assignment: Assignment) {
|
||||
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
||||
@ -32,6 +29,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
|
||||
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
|
||||
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, origtarget.datatype, origtarget.scope,
|
||||
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
|
||||
} else origtarget
|
||||
}
|
||||
|
||||
fun translateNormalAssignment(assign: AsmAssignment) {
|
||||
if(assign.isAugmentable) {
|
||||
augmentableAsmGen.translate(assign)
|
||||
@ -43,9 +47,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
// simple case: assign a constant number
|
||||
val num = assign.source.number!!.number
|
||||
when (assign.target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
|
||||
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toInt())
|
||||
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
|
||||
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
|
||||
DataType.FLOAT -> assignConstantFloat(assign.target, num)
|
||||
else -> throw AssemblyError("weird numval type")
|
||||
}
|
||||
}
|
||||
@ -131,7 +135,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val value = assign.source.memory!!
|
||||
when (value.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
|
||||
assignMemoryByte(assign.target, address, null)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -162,7 +166,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
when (val sub = value.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
asmgen.saveXbeforeCall(value)
|
||||
asmgen.translateFunctionCall(value)
|
||||
asmgen.translateFunctionCall(value, true)
|
||||
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
|
||||
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
|
||||
when (returnValue.first) {
|
||||
@ -261,11 +265,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
assign.target,
|
||||
false, program.memsizer, assign.position
|
||||
))
|
||||
val target = virtualRegsToVariables(assign.target)
|
||||
when(value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> augmentableAsmGen.inplaceNegate(assign.target, assign.target.datatype)
|
||||
"~" -> augmentableAsmGen.inplaceInvert(assign.target, assign.target.datatype)
|
||||
"not" -> augmentableAsmGen.inplaceBooleanNot(assign.target, assign.target.datatype)
|
||||
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
|
||||
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
|
||||
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
}
|
||||
@ -273,7 +278,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
// Everything else just evaluate via the stack.
|
||||
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
|
||||
// because the code here is the implementation of exactly that...)
|
||||
// TODO FIX THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||
asmgen.translateExpression(value)
|
||||
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||
@ -345,7 +350,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
when (value.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
|
||||
assignMemoryByteIntoWord(target, address, null)
|
||||
return
|
||||
}
|
||||
@ -445,9 +450,35 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
// No more special optmized cases yet. Do the rest via more complex evaluation
|
||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
||||
asmgen.out(" jsr floats.FREADUY")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, true)
|
||||
asmgen.out(" jsr floats.FREADSA")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||
asmgen.out(" jsr floats.GIVUAYFAY")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" jsr floats.GIVAYFAY")
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
if(target.register==RegisterOrPair.FAC2) {
|
||||
asmgen.out(" jsr floats.MOVEF")
|
||||
}
|
||||
} else {
|
||||
// No more special optmized cases yet. Do the rest via more complex evaluation
|
||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
|
||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
|
||||
@ -779,7 +810,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if(target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
|
||||
@ -989,7 +1020,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
TargetStorageKind.ARRAY -> {
|
||||
target.array!!
|
||||
if(target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
@ -1044,7 +1075,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.copy_float""")
|
||||
+ jsr floats.copy_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
@ -1078,6 +1109,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFAC2float(target: AsmAssignTarget) {
|
||||
asmgen.out(" jsr floats.MOVFA") // fac2 -> fac1
|
||||
assignFAC1float(target)
|
||||
}
|
||||
|
||||
internal fun assignFAC1float(target: AsmAssignTarget) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -1213,7 +1249,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
else {
|
||||
@ -1266,7 +1302,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (wordtarget.constArrayIndexValue!=null) {
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
|
||||
asmgen.out(" lda $sourceName")
|
||||
asmgen.signExtendAYlsb(DataType.BYTE)
|
||||
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||
@ -1334,7 +1370,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (wordtarget.constArrayIndexValue!=null) {
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
|
||||
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||
@ -1494,7 +1530,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (target.constArrayIndexValue!=null) {
|
||||
val idx = target.constArrayIndexValue!! * 2
|
||||
val idx = target.constArrayIndexValue!! * 2u
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname}+$idx | stx ${target.asmVarname}+$idx+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
|
||||
@ -1715,8 +1751,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
|
||||
if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
private fun assignConstantByte(target: AsmAssignTarget, byte: Int) {
|
||||
if(byte==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
// optimize setting zero value for this cpu
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -1949,7 +1985,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignMemoryByte(target: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
|
||||
private fun assignMemoryByte(target: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
|
||||
if (address != null) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -2033,7 +2069,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
|
||||
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
|
||||
if (address != null) {
|
||||
when(wordtarget.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -2125,7 +2161,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
val scopedName = vardecl.scopedName.joinToString(".")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if (asmgen.isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
|
@ -7,12 +7,11 @@ import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
|
||||
import prog8.compilerinterface.CpuType
|
||||
|
||||
|
||||
internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private val assignmentAsmGen: AssignmentAsmGen,
|
||||
private val exprAsmGen: ExpressionsAsmGen,
|
||||
private val asmgen: AsmGen
|
||||
) {
|
||||
fun translate(assign: AsmAssignment) {
|
||||
@ -22,15 +21,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when (val value = assign.source.expression!!) {
|
||||
is PrefixExpression -> {
|
||||
// A = -A , A = +A, A = ~A, A = not A
|
||||
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
|
||||
val itype = value.inferType(program)
|
||||
if(!itype.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val type = itype.getOr(DataType.UNDEFINED)
|
||||
when (value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> inplaceNegate(assign.target, type)
|
||||
"~" -> inplaceInvert(assign.target, type)
|
||||
"not" -> inplaceBooleanNot(assign.target, type)
|
||||
"-" -> inplaceNegate(target, type)
|
||||
"~" -> inplaceInvert(target, type)
|
||||
"not" -> inplaceBooleanNot(target, type)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
}
|
||||
@ -102,10 +102,66 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
throw FatalAstException("assignment should be augmentable $binExpr")
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if(leftBinExpr!=null && rightBinExpr==null) {
|
||||
if(leftBinExpr.left isSameAs astTarget) {
|
||||
// X = (X <oper> Right) <oper> Something
|
||||
inplaceModification(target, leftBinExpr.operator, leftBinExpr.right)
|
||||
inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
return
|
||||
}
|
||||
if(leftBinExpr.right isSameAs astTarget) {
|
||||
// X = (Left <oper> X) <oper> Something
|
||||
if(leftBinExpr.operator in associativeOperators) {
|
||||
inplaceModification(target, leftBinExpr.operator, leftBinExpr.left)
|
||||
inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
return
|
||||
} else {
|
||||
throw AssemblyError("operands in wrong order for non-associative operator")
|
||||
}
|
||||
}
|
||||
}
|
||||
if(leftBinExpr==null && rightBinExpr!=null) {
|
||||
if(rightBinExpr.left isSameAs astTarget) {
|
||||
// X = Something <oper> (X <oper> Right)
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
inplaceModification(target, rightBinExpr.operator, rightBinExpr.right)
|
||||
inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
return
|
||||
} else {
|
||||
throw AssemblyError("operands in wrong order for non-associative operator")
|
||||
}
|
||||
}
|
||||
if(rightBinExpr.right isSameAs astTarget) {
|
||||
// X = Something <oper> (Left <oper> X)
|
||||
if(binExpr.operator in associativeOperators && rightBinExpr.operator in associativeOperators) {
|
||||
inplaceModification(target, rightBinExpr.operator, rightBinExpr.left)
|
||||
inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
return
|
||||
} else {
|
||||
throw AssemblyError("operands in wrong order for non-associative operator")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw FatalAstException("assignment should follow augmentable rules $binExpr")
|
||||
}
|
||||
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: Expression) {
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: Expression) {
|
||||
|
||||
// the asm-gen code can deal with situations where you want to assign a byte into a word.
|
||||
// it will create the most optimized code to do this (so it type-extends for us).
|
||||
// But we can't deal with writing a word into a byte - explicit typeconversion is required
|
||||
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOr(DataType.UNDEFINED)) > program.memsizer.memorySize(target.datatype)) {
|
||||
val typecast = TypecastExpression(origValue, target.datatype, true, origValue.position)
|
||||
typecast.linkParents(origValue.parent)
|
||||
typecast
|
||||
}
|
||||
else {
|
||||
origValue
|
||||
}
|
||||
|
||||
val valueLv = (value as? NumericLiteralValue)?.number
|
||||
val ident = value as? IdentifierReference
|
||||
val memread = value as? DirectMemoryRead
|
||||
@ -181,9 +237,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// TODO OTHER EVALUATION HERE, don't use the estack
|
||||
// TODO OTHER EVALUATION HERE, don't use the estack to transfer the address to read/write from
|
||||
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1") // TODO don't use estack to transfer the address to read from
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
|
||||
@ -194,7 +250,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
|
||||
}
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx") // TODO don't use estack to transfer the address to read from
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,8 +345,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg in-place modification")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack in-place modification")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("no asm gen for stack in-place modification")
|
||||
}
|
||||
}
|
||||
|
||||
@ -657,14 +713,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $name
|
||||
sta $name""")
|
||||
}
|
||||
"-" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
@ -673,15 +729,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta $name""")
|
||||
}
|
||||
"|", "or" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" ora $name | sta $name")
|
||||
}
|
||||
"&", "and" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" and $name | sta $name")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
// TODO: tuned code for more operators
|
||||
@ -694,7 +750,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $name
|
||||
@ -704,7 +760,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
"-" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
@ -716,11 +772,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
"|", "or" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" ora $name | sta $name")
|
||||
}
|
||||
"&", "and" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" and $name | sta $name")
|
||||
if(dt in WordDatatypes) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
@ -730,7 +786,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
"^", "xor" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
// TODO: tuned code for more operators
|
||||
@ -1103,7 +1159,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta $name
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $name+1
|
||||
""") }
|
||||
""")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
@ -1790,8 +1847,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else -> throw AssemblyError("invalid reg dt for byte not")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for byte stack not")
|
||||
else -> throw AssemblyError("missing codegen for in-place not of ubyte ${target.kind}")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack not")
|
||||
else -> throw AssemblyError("no asm gen for in-place not of ubyte ${target.kind}")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@ -1843,13 +1900,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+ ldx #1
|
||||
+""")
|
||||
}
|
||||
in Cx16VirtualRegisters -> TODO()
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
else -> throw AssemblyError("invalid reg dt for word not")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory not")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for word stack not")
|
||||
else -> throw AssemblyError("missing codegen for in-place not of uword for ${target.kind}")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack not")
|
||||
else -> throw AssemblyError("no asm gen for in-place not of uword for ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("boolean-not of invalid type")
|
||||
@ -1899,8 +1955,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else -> throw AssemblyError("invalid reg dt for byte invert")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for byte stack invert")
|
||||
else -> throw AssemblyError("missing codegen for in-place invert ubyte for ${target.kind}")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
|
||||
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@ -1919,15 +1975,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
|
||||
in Cx16VirtualRegisters -> {
|
||||
TODO("codegen for cx16 word register invert")
|
||||
}
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
else -> throw AssemblyError("invalid reg dt for word invert")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory invert")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for word stack invert")
|
||||
else -> throw AssemblyError("missing codegen for in-place invert uword for ${target.kind}")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
|
||||
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invert of invalid type")
|
||||
@ -1947,15 +2000,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" sta P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1")
|
||||
RegisterOrPair.X -> asmgen.out(" stx P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tax")
|
||||
RegisterOrPair.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tay")
|
||||
RegisterOrPair.A -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" eor #255 | ina")
|
||||
else
|
||||
asmgen.out(" eor #255 | clc | adc #1")
|
||||
|
||||
}
|
||||
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax | inx")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay | iny")
|
||||
else -> throw AssemblyError("invalid reg dt for byte negate")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("can't in-place negate memory ubyte")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for byte stack negate")
|
||||
else -> throw AssemblyError("missing codegen for in-place negate byte array")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
|
||||
else -> throw AssemblyError("no asm gen for in-place negate byte")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
@ -2010,15 +2069,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sbc P8ZP_SCRATCH_REG+1
|
||||
tay""")
|
||||
}
|
||||
in Cx16VirtualRegisters -> {
|
||||
TODO("codegen for cx16 word register negate")
|
||||
}
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
else -> throw AssemblyError("invalid reg dt for word neg")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("no asm gen for word memory negate")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for word stack negate")
|
||||
else -> throw AssemblyError("missing codegen for in-place negate word array")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
|
||||
else -> throw AssemblyError("no asm gen for in-place negate word")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
@ -2031,13 +2087,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> TODO("missing codegen for float reg negate")
|
||||
TargetStorageKind.MEMORY -> TODO("missing codegen for float memory negate")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for stack float negate")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for stack float negate")
|
||||
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("negate of invalid type")
|
||||
else -> throw AssemblyError("negate of invalid type $dt")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,12 +16,12 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0x8000
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801u
|
||||
override val RAW_LOAD_ADDRESS = 0x8000u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
|
||||
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
|
||||
override val ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
||||
override val ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
@ -67,7 +67,7 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
@ -89,10 +89,10 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x7a // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
|
||||
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
init {
|
||||
@ -103,14 +103,14 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22..0xff)
|
||||
free.addAll(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
free.addAll(0xa9..0xff)
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
|
@ -1,15 +1,11 @@
|
||||
package prog8tests.asmgen
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.RegisterOrPair
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
@ -25,22 +21,19 @@ import prog8tests.asmgen.helpers.DummyStringEncoder
|
||||
import prog8tests.asmgen.helpers.ErrorReporterForTests
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAsmGen6502 {
|
||||
|
||||
private fun createTestProgram(): Program {
|
||||
class AsmGenSymbolsTests: StringSpec({
|
||||
fun createTestProgram(): Program {
|
||||
/*
|
||||
main {
|
||||
main {
|
||||
|
||||
label_outside:
|
||||
label_outside:
|
||||
uword var_outside
|
||||
|
||||
sub start () {
|
||||
uword localvar = 1234
|
||||
uword tgt
|
||||
|
||||
locallabel:
|
||||
locallabel:
|
||||
tgt = localvar
|
||||
tgt = &locallabel
|
||||
tgt = &var_outside
|
||||
@ -50,11 +43,11 @@ locallabel:
|
||||
tgt = &main.var_outside
|
||||
tgt = &main.label_outside
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, Position.DUMMY)
|
||||
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, null, Position.DUMMY)
|
||||
val labelInSub = Label("locallabel", Position.DUMMY)
|
||||
|
||||
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
@ -70,17 +63,16 @@ locallabel:
|
||||
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
|
||||
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
|
||||
val labelInBlock = Label("label_outside", Position.DUMMY)
|
||||
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
|
||||
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, null, Position.DUMMY)
|
||||
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
|
||||
|
||||
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
return program
|
||||
}
|
||||
|
||||
private fun createTestAsmGen(program: Program): AsmGen {
|
||||
fun createTestAsmGen(program: Program): AsmGen {
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
|
||||
val zp = C64MachineDefinition.C64Zeropage(options)
|
||||
@ -88,64 +80,93 @@ locallabel:
|
||||
return asmgen
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSymbolNameFromStrings() {
|
||||
"symbol and variable names from strings" {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
|
||||
assertThat(asmgen.asmSymbolName("name"), equalTo("name"))
|
||||
assertThat(asmgen.asmSymbolName("<name>"), equalTo("prog8_name"))
|
||||
assertThat(asmgen.asmSymbolName(RegisterOrPair.R15), equalTo("cx16.r15"))
|
||||
assertThat(asmgen.asmSymbolName(listOf("a", "b", "name")), equalTo("a.b.name"))
|
||||
assertThat(asmgen.asmVariableName("name"), equalTo("name"))
|
||||
assertThat(asmgen.asmVariableName("<name>"), equalTo("prog8_name"))
|
||||
assertThat(asmgen.asmVariableName(listOf("a", "b", "name")), equalTo("a.b.name"))
|
||||
asmgen.asmSymbolName("name") shouldBe "name"
|
||||
asmgen.asmSymbolName("name") shouldBe "name"
|
||||
asmgen.asmSymbolName("<name>") shouldBe "prog8_name"
|
||||
asmgen.asmSymbolName(RegisterOrPair.R15) shouldBe "cx16.r15"
|
||||
asmgen.asmSymbolName(listOf("a", "b", "name")) shouldBe "a.b.name"
|
||||
asmgen.asmVariableName("name") shouldBe "name"
|
||||
asmgen.asmVariableName("<name>") shouldBe "prog8_name"
|
||||
asmgen.asmVariableName(listOf("a", "b", "name")) shouldBe "a.b.name"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSymbolNameFromVarIdentifier() {
|
||||
"symbol and variable names from variable identifiers" {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local variable
|
||||
val localvarIdent = sub.statements.filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
|
||||
assertThat(asmgen.asmSymbolName(localvarIdent), equalTo("localvar"))
|
||||
assertThat(asmgen.asmVariableName(localvarIdent), equalTo("localvar"))
|
||||
asmgen.asmSymbolName(localvarIdent) shouldBe "localvar"
|
||||
asmgen.asmVariableName(localvarIdent) shouldBe "localvar"
|
||||
val localvarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "start", "localvar") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(localvarIdentScoped), equalTo("main.start.localvar"))
|
||||
assertThat(asmgen.asmVariableName(localvarIdentScoped), equalTo("main.start.localvar"))
|
||||
asmgen.asmSymbolName(localvarIdentScoped) shouldBe "main.start.localvar"
|
||||
asmgen.asmVariableName(localvarIdentScoped) shouldBe "main.start.localvar"
|
||||
|
||||
// variable from outer scope (note that for Variables, no scoping prefix symbols are required,
|
||||
// because they're not outputted as locally scoped symbols for the assembler
|
||||
val scopedVarIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("var_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedVarIdent), equalTo("main.var_outside"))
|
||||
assertThat(asmgen.asmVariableName(scopedVarIdent), equalTo("var_outside"))
|
||||
asmgen.asmSymbolName(scopedVarIdent) shouldBe "main.var_outside"
|
||||
asmgen.asmVariableName(scopedVarIdent) shouldBe "var_outside"
|
||||
val scopedVarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "var_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedVarIdentScoped), equalTo("main.var_outside"))
|
||||
assertThat(asmgen.asmVariableName(scopedVarIdentScoped), equalTo("main.var_outside"))
|
||||
asmgen.asmSymbolName(scopedVarIdentScoped) shouldBe "main.var_outside"
|
||||
asmgen.asmVariableName(scopedVarIdentScoped) shouldBe "main.var_outside"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSymbolNameFromLabelIdentifier() {
|
||||
"symbol and variable names from label identifiers" {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local label
|
||||
val localLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("locallabel") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(localLabelIdent), equalTo("_locallabel"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdent), equalTo("locallabel"))
|
||||
asmgen.asmSymbolName(localLabelIdent) shouldBe "_locallabel"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(localLabelIdent) shouldBe "locallabel"
|
||||
}
|
||||
val localLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","start","locallabel") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(localLabelIdentScoped), equalTo("main.start._locallabel"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdentScoped), equalTo("main.start.locallabel"))
|
||||
asmgen.asmSymbolName(localLabelIdentScoped) shouldBe "main.start._locallabel"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(localLabelIdentScoped) shouldBe "main.start.locallabel"
|
||||
}
|
||||
|
||||
// label from outer scope needs sope prefixes because it is outputted as a locally scoped symbol for the assembler
|
||||
val scopedLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("label_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedLabelIdent), equalTo("main._label_outside"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdent), equalTo("label_outside"))
|
||||
asmgen.asmSymbolName(scopedLabelIdent) shouldBe "main._label_outside"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(scopedLabelIdent) shouldBe "label_outside"
|
||||
}
|
||||
val scopedLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","label_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedLabelIdentScoped), equalTo("main._label_outside"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdentScoped), equalTo("main.label_outside"))
|
||||
asmgen.asmSymbolName(scopedLabelIdentScoped) shouldBe "main._label_outside"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(scopedLabelIdentScoped) shouldBe "main.label_outside"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"asm names for hooks to zp temp vars" {
|
||||
/*
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
prog8_lib.P8ZP_SCRATCH_REG = 1
|
||||
prog8_lib.P8ZP_SCRATCH_B1 = 1
|
||||
prog8_lib.P8ZP_SCRATCH_W1 = 1
|
||||
prog8_lib.P8ZP_SCRATCH_W2 = 1
|
||||
*/
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
asmgen.asmSymbolName("prog8_lib.P8ZP_SCRATCH_REG") shouldBe "P8ZP_SCRATCH_REG"
|
||||
asmgen.asmSymbolName("prog8_lib.P8ZP_SCRATCH_W2") shouldBe "P8ZP_SCRATCH_W2"
|
||||
asmgen.asmSymbolName(listOf("prog8_lib","P8ZP_SCRATCH_REG")) shouldBe "P8ZP_SCRATCH_REG"
|
||||
asmgen.asmSymbolName(listOf("prog8_lib","P8ZP_SCRATCH_W2")) shouldBe "P8ZP_SCRATCH_W2"
|
||||
val id1 = IdentifierReference(listOf("prog8_lib","P8ZP_SCRATCH_REG"), Position.DUMMY)
|
||||
id1.linkParents(program.toplevelModule)
|
||||
val id2 = IdentifierReference(listOf("prog8_lib","P8ZP_SCRATCH_W2"), Position.DUMMY)
|
||||
id2.linkParents(program.toplevelModule)
|
||||
asmgen.asmSymbolName(id1) shouldBe "P8ZP_SCRATCH_REG"
|
||||
asmgen.asmSymbolName(id2) shouldBe "P8ZP_SCRATCH_W2"
|
||||
}
|
||||
})
|
8
codeGeneration/test/ProjectConfig.kt
Normal file
8
codeGeneration/test/ProjectConfig.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package prog8tests.asmgen
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import kotlin.math.max
|
||||
|
||||
object ProjectConfig : AbstractProjectConfig() {
|
||||
override val parallelism = max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
}
|
@ -23,15 +23,15 @@ internal val DummyFunctions = object : IBuiltinFunctions {
|
||||
}
|
||||
|
||||
internal val DummyMemsizer = object : IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int = 0
|
||||
override fun memorySize(dt: DataType) = 0
|
||||
}
|
||||
|
||||
internal val DummyStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,6 @@ dependencies {
|
||||
implementation project(':compilerAst')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -33,22 +27,6 @@ sourceSets {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
// note: there are no unit tests in this module!
|
||||
|
@ -12,7 +12,5 @@
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -1,2 +1,2 @@
|
||||
Unittests for things in this module are located in the Compiler module instead,
|
||||
for convenience sake - and to not spread the test cases around too much.
|
||||
for convenience sake, and to not spread the test cases around too much.
|
@ -4,7 +4,10 @@ import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.augmentAssignmentOperators
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
@ -12,39 +15,19 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.isInRegularRAMof
|
||||
import prog8.compilerinterface.isIOAddress
|
||||
|
||||
|
||||
class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: [ IS THIS STILL TRUE AFTER ALL CHANGES? ]
|
||||
// if(decl.type==VarDeclType.VAR ) {
|
||||
// val binExpr = decl.value as? BinaryExpression
|
||||
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
|
||||
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
|
||||
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
|
||||
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
// val assign = Assignment(target, augExpr, binExpr.position)
|
||||
// println("SPLIT VARDECL $decl")
|
||||
// return listOf(
|
||||
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
|
||||
// IAstModification.InsertAfter(decl, assign, parent)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(assignment.value.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
|
||||
return noModifications
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
|
||||
if(binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
|
||||
return noModifications
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||
@ -63,10 +46,34 @@ X = BinExpr X = LeftExpr
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
|
||||
if(assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
if(assignment.target isSameAs binExpr.left) {
|
||||
if(binExpr.right.isSimple)
|
||||
return noModifications
|
||||
val leftBx = binExpr.left as? BinaryExpression
|
||||
if(leftBx!=null && (!leftBx.left.isSimple || !leftBx.right.isSimple))
|
||||
return noModifications
|
||||
val rightBx = binExpr.right as? BinaryExpression
|
||||
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
|
||||
return noModifications
|
||||
|
||||
if(binExpr.right.isSimple && !assignment.isAugmentable) {
|
||||
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
|
||||
// val dt = assignment.target.inferType(program)
|
||||
// if(!dt.isInteger)
|
||||
// return noModifications
|
||||
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
|
||||
// val assignTempVar = Assignment(
|
||||
// AssignTarget(tempVar, null, null, binExpr.right.position),
|
||||
// binExpr.right, binExpr.right.position
|
||||
// )
|
||||
// return listOf(
|
||||
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
|
||||
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
|
||||
// )
|
||||
}
|
||||
|
||||
if(binExpr.right.isSimple) {
|
||||
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
@ -78,16 +85,43 @@ X = BinExpr X = LeftExpr
|
||||
}
|
||||
|
||||
// TODO further unraveling of binary expression trees into flat statements.
|
||||
// however this should probably be done in a more generic way to also service
|
||||
// however this should probably be done in a more generic way to also work on
|
||||
// the expressiontrees that are not used in an assignment statement...
|
||||
}
|
||||
|
||||
val typecast = assignment.value as? TypecastExpression
|
||||
if(typecast!=null) {
|
||||
val origExpr = typecast.expression as? BinaryExpression
|
||||
if(origExpr!=null) {
|
||||
// it's a typecast of a binary expression.
|
||||
// we can see if we can unwrap the binary expression by working on a new temporary variable
|
||||
// (that has the type of the expression), and then finally doing the typecast.
|
||||
// Once it's outside the typecast, the regular splitting can commence.
|
||||
val tempVar = when(val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> listOf("prog8_lib", "retval_interm_ub")
|
||||
DataType.BYTE -> listOf("prog8_lib", "retval_interm_b")
|
||||
DataType.UWORD -> listOf("prog8_lib", "retval_interm_uw")
|
||||
DataType.WORD -> listOf("prog8_lib", "retval_interm_w")
|
||||
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
|
||||
else -> throw FatalAstException("invalid dt $tempDt")
|
||||
}
|
||||
val assignTempVar = Assignment(
|
||||
AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position),
|
||||
typecast.expression, typecast.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVar, typecast.position), typecast)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget) =
|
||||
if (target.identifier!=null || target.memoryAddress!=null)
|
||||
target.isInRegularRAMof(compTarget.machine)
|
||||
!target.isIOAddress(compTarget.machine)
|
||||
else
|
||||
false
|
||||
|
||||
|
@ -46,14 +46,14 @@ class ConstExprEvaluator {
|
||||
left.number.toInt().ushr(amount.number.toInt())
|
||||
else
|
||||
left.number.toInt().shr(amount.number.toInt())
|
||||
return NumericLiteralValue(left.type, result, left.position)
|
||||
return NumericLiteralValue(left.type, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
|
||||
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
||||
throw ExpressionError("cannot compute $left << $amount", left.position)
|
||||
val result = left.number.toInt().shl(amount.number.toInt())
|
||||
return NumericLiteralValue(left.type, result, left.position)
|
||||
return NumericLiteralValue(left.type, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
@ -61,12 +61,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -78,12 +78,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -95,12 +95,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -110,11 +110,11 @@ class ConstExprEvaluator {
|
||||
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toShort(), left.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() xor right.number.toInt(), left.position)
|
||||
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||
@ -123,11 +123,11 @@ class ConstExprEvaluator {
|
||||
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
|
||||
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left | $right", left.position)
|
||||
@ -136,11 +136,11 @@ class ConstExprEvaluator {
|
||||
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
|
||||
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||
@ -151,12 +151,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -168,12 +168,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number + right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -185,12 +185,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number - right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -202,12 +202,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number * right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -227,19 +227,19 @@ class ConstExprEvaluator {
|
||||
NumericLiteralValue.optimalInteger(result, left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
|
||||
if(right.number==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number / right.number.toInt(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number / right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@ -256,19 +256,19 @@ class ConstExprEvaluator {
|
||||
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number % right.number.toInt(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number % right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
@ -48,29 +48,29 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
"~" -> when (subexpr.type) {
|
||||
DataType.BYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
|
||||
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
|
||||
NumericLiteralValue(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
|
||||
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
|
||||
NumericLiteralValue(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
}
|
||||
"not" -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
|
||||
NumericLiteralValue.fromBoolean(subexpr.number == 0.0, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
@ -79,7 +79,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Try to constfold a binary expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
||||
@ -101,23 +101,50 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
val rightconst = expr.right.constValue(program)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
if(expr.operator=="==" && rightconst!=null) {
|
||||
val leftExpr = expr.left as? BinaryExpression
|
||||
if(leftExpr!=null) {
|
||||
val leftRightConst = leftExpr.right.constValue(program)
|
||||
if(leftRightConst!=null) {
|
||||
when (leftExpr.operator) {
|
||||
"+" -> {
|
||||
// X + С1 == C2 --> X == C2 - C1
|
||||
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
|
||||
)
|
||||
}
|
||||
"-" -> {
|
||||
// X - С1 == C2 --> X == C2 + C1
|
||||
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "**" && leftconst!=null) {
|
||||
// optimize various simple cases of ** :
|
||||
// optimize away 1 ** x into just 1 and 0 ** x into just 0
|
||||
// optimize 2 ** x into (1<<x) if both operands are integer.
|
||||
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
|
||||
when (leftconst.number.toDouble()) {
|
||||
when (leftconst.number) {
|
||||
0.0 -> {
|
||||
val value = NumericLiteralValue(leftDt, 0, expr.position)
|
||||
val value = NumericLiteralValue(leftDt, 0.0, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
}
|
||||
1.0 -> {
|
||||
val value = NumericLiteralValue(leftDt, 1, expr.position)
|
||||
val value = NumericLiteralValue(leftDt, 1.0, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
}
|
||||
2.0 -> {
|
||||
if(rightconst!=null) {
|
||||
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
|
||||
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number), expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
} else {
|
||||
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
|
||||
@ -128,7 +155,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
is VarDecl -> parent.datatype
|
||||
else -> leftDt
|
||||
}
|
||||
val one = NumericLiteralValue(targetDt, 1, expr.position)
|
||||
val one = NumericLiteralValue(targetDt, 1.0, expr.position)
|
||||
val shift = BinaryExpression(one, "<<", expr.right, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, shift, parent)
|
||||
}
|
||||
@ -159,13 +186,52 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
val evaluator = ConstExprEvaluator()
|
||||
|
||||
// const fold when both operands are a const
|
||||
if(leftconst != null && rightconst != null) {
|
||||
val evaluator = ConstExprEvaluator()
|
||||
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
modifications += IAstModification.ReplaceNode(expr, result, parent)
|
||||
}
|
||||
|
||||
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
if(expr.operator=="+" || expr.operator=="-") {
|
||||
if(leftBinExpr!=null && rightBinExpr!=null) {
|
||||
val c1 = leftBinExpr.right.constValue(program)
|
||||
val c2 = rightBinExpr.right.constValue(program)
|
||||
if(leftBinExpr.operator=="+" && rightBinExpr.operator=="+") {
|
||||
if (c1 != null && c2 != null) {
|
||||
// (X + C1) <plusmin> (Y + C2) => (X <plusmin> Y) + (C1 <plusmin> C2)
|
||||
val c3 = evaluator.evaluate(c1, expr.operator, c2)
|
||||
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
|
||||
val newExpr = BinaryExpression(xwithy, "+", c3, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
|
||||
}
|
||||
}
|
||||
else if(leftBinExpr.operator=="-" && rightBinExpr.operator=="-") {
|
||||
if (c1 != null && c2 != null) {
|
||||
// (X - C1) <plusmin> (Y - C2) => (X <plusmin> Y) - (C1 <plusmin> C2)
|
||||
val c3 = evaluator.evaluate(c1, expr.operator, c2)
|
||||
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
|
||||
val newExpr = BinaryExpression(xwithy, "-", c3, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
|
||||
}
|
||||
}
|
||||
else if(leftBinExpr.operator=="*" && rightBinExpr.operator=="*"){
|
||||
if (c1 != null && c2 != null && c1==c2) {
|
||||
//(X * C) <plusmin> (Y * C) => (X <plusmin> Y) * C
|
||||
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
|
||||
val newExpr = BinaryExpression(xwithy, "*", c1, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
|
@ -24,11 +24,16 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
try {
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& declConstValue.inferType(program) isnot decl.datatype) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
||||
&& declConstValue.type != decl.datatype) {
|
||||
// avoid silent float roundings
|
||||
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
|
||||
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position)
|
||||
} else {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
if (cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
||||
}
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
@ -38,9 +43,9 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
}
|
||||
|
||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
val from = range.from.constValue(program)?.number?.toDouble()
|
||||
val to = range.to.constValue(program)?.number?.toDouble()
|
||||
val step = range.step.constValue(program)?.number?.toDouble()
|
||||
val from = range.from.constValue(program)?.number
|
||||
val to = range.to.constValue(program)?.number
|
||||
val step = range.step.constValue(program)?.number
|
||||
|
||||
if(from==null) {
|
||||
if(!range.from.inferType(program).isInteger)
|
||||
@ -105,8 +110,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true) {
|
||||
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return noModifications
|
||||
}
|
||||
@ -132,7 +136,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number, litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
@ -142,17 +146,17 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
@ -185,7 +189,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
@ -210,7 +214,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = numericLv.number.toDouble()
|
||||
val fillvalue = numericLv.number
|
||||
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
|
@ -19,17 +19,6 @@ import kotlin.math.pow
|
||||
|
||||
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||
|
||||
*(&X) => X
|
||||
X % 1 => 0
|
||||
X / 1 => X
|
||||
X ^ -1 => ~x
|
||||
X >= 1 => X > 0
|
||||
X < 1 => X <= 0
|
||||
X + С1 == C2 => X == C2 - C1
|
||||
((X + C1) + C2) => (X + (C1 + C2))
|
||||
((X + C1) + (Y + C2)) => ((X + Y) + (C1 + C2))
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@ -160,7 +149,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
if (y != null) {
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
|
||||
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
@ -170,7 +159,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
if (y != null) {
|
||||
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
||||
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
|
||||
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
@ -190,14 +179,26 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0) {
|
||||
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
|
||||
// for integers: x >= 1 --> x > 0
|
||||
expr.operator = ">"
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0) {
|
||||
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
|
||||
// for integers: x < 1 --> x <= 0
|
||||
expr.operator = "<="
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0.0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||
@ -231,52 +232,62 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||
val newExpr: Expression? = when (expr.operator) {
|
||||
"or" -> {
|
||||
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue))
|
||||
constTrue
|
||||
else if (leftVal != null && !leftVal.asBooleanValue)
|
||||
expr.right
|
||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||
expr.left
|
||||
else
|
||||
null
|
||||
when {
|
||||
leftVal != null && leftVal.asBooleanValue || rightVal != null && rightVal.asBooleanValue -> constTrue
|
||||
leftVal != null && !leftVal.asBooleanValue -> expr.right
|
||||
rightVal != null && !rightVal.asBooleanValue -> expr.left
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"and" -> {
|
||||
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue))
|
||||
constFalse
|
||||
else if (leftVal != null && leftVal.asBooleanValue)
|
||||
expr.right
|
||||
else if (rightVal != null && rightVal.asBooleanValue)
|
||||
expr.left
|
||||
else
|
||||
null
|
||||
when {
|
||||
leftVal != null && !leftVal.asBooleanValue || rightVal != null && !rightVal.asBooleanValue -> constFalse
|
||||
leftVal != null && leftVal.asBooleanValue -> expr.right
|
||||
rightVal != null && rightVal.asBooleanValue -> expr.left
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"xor" -> {
|
||||
if (leftVal != null && !leftVal.asBooleanValue)
|
||||
expr.right
|
||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||
expr.left
|
||||
else if (leftVal != null && leftVal.asBooleanValue)
|
||||
PrefixExpression("not", expr.right, expr.right.position)
|
||||
else if (rightVal != null && rightVal.asBooleanValue)
|
||||
PrefixExpression("not", expr.left, expr.left.position)
|
||||
else
|
||||
null
|
||||
when {
|
||||
leftVal != null && !leftVal.asBooleanValue -> expr.right
|
||||
rightVal != null && !rightVal.asBooleanValue -> expr.left
|
||||
leftVal != null && leftVal.asBooleanValue -> PrefixExpression("not", expr.right, expr.right.position)
|
||||
rightVal != null && rightVal.asBooleanValue -> PrefixExpression("not", expr.left, expr.left.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"|", "^" -> {
|
||||
if (leftVal != null && !leftVal.asBooleanValue)
|
||||
expr.right
|
||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||
expr.left
|
||||
else
|
||||
null
|
||||
"|" -> {
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, rightVal.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, rightVal.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, leftVal.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"^" -> {
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"&" -> {
|
||||
if (leftVal != null && !leftVal.asBooleanValue)
|
||||
constFalse
|
||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||
constFalse
|
||||
else
|
||||
null
|
||||
when {
|
||||
leftVal?.number==0.0 -> constFalse
|
||||
rightVal?.number==0.0 -> constFalse
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
|
||||
"/" -> optimizeDivision(expr, leftVal, rightVal)
|
||||
@ -320,7 +331,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// useless msb() of byte value that was typecasted to word, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0, arg.expression.position),
|
||||
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
|
||||
parent))
|
||||
}
|
||||
} else {
|
||||
@ -329,7 +340,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// useless msb() of byte value, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0, arg.position),
|
||||
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
|
||||
parent))
|
||||
}
|
||||
}
|
||||
@ -362,7 +373,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (rightVal2 != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal2
|
||||
when (rightConst.number.toDouble()) {
|
||||
when (rightConst.number) {
|
||||
0.0 -> {
|
||||
// left
|
||||
return expr2.left
|
||||
@ -371,7 +382,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
// no need to check for left val constant (because of associativity)
|
||||
|
||||
val rnum = rightVal?.number?.toDouble()
|
||||
val rnum = rightVal?.number
|
||||
if(rnum!=null && rnum<0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
@ -392,7 +403,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rnum = rightVal.number.toDouble()
|
||||
val rnum = rightVal.number
|
||||
if (rnum == 0.0) {
|
||||
// left
|
||||
return expr.left
|
||||
@ -406,7 +417,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
if (leftVal != null) {
|
||||
// left value is a constant, see if we can optimize
|
||||
when (leftVal.number.toDouble()) {
|
||||
when (leftVal.number) {
|
||||
0.0 -> {
|
||||
// -right
|
||||
return PrefixExpression("-", expr.right, expr.position)
|
||||
@ -425,7 +436,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
when (rightConst.number.toDouble()) {
|
||||
when (rightConst.number) {
|
||||
-3.0 -> {
|
||||
// -1/(left*left*left)
|
||||
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||
@ -445,7 +456,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
0.0 -> {
|
||||
// 1
|
||||
return NumericLiteralValue(rightConst.type, 1, expr.position)
|
||||
return NumericLiteralValue(rightConst.type, 1.0, expr.position)
|
||||
}
|
||||
0.5 -> {
|
||||
// sqrt(left)
|
||||
@ -467,18 +478,18 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
if (leftVal != null) {
|
||||
// left value is a constant, see if we can optimize
|
||||
when (leftVal.number.toDouble()) {
|
||||
when (leftVal.number) {
|
||||
-1.0 -> {
|
||||
// -1
|
||||
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
|
||||
}
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
//1
|
||||
return NumericLiteralValue(leftVal.type, 1, expr.position)
|
||||
return NumericLiteralValue(leftVal.type, 1.0, expr.position)
|
||||
}
|
||||
|
||||
}
|
||||
@ -500,7 +511,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0, expr.position)
|
||||
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
|
||||
} else if (cv in powersOfTwo) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
@ -520,7 +531,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
val cv = rightConst.number.toDouble()
|
||||
val cv = rightConst.number
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
if (!leftIDt.isKnown)
|
||||
return null
|
||||
@ -555,22 +566,22 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
if (leftDt == DataType.UBYTE) {
|
||||
if (abs(rightConst.number.toDouble()) >= 256.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||
if (abs(rightConst.number) >= 256.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
} else if (leftDt == DataType.UWORD) {
|
||||
if (abs(rightConst.number.toDouble()) >= 65536.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||
if (abs(rightConst.number) >= 65536.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (leftVal != null) {
|
||||
// left value is a constant, see if we can optimize
|
||||
when (leftVal.number.toDouble()) {
|
||||
when (leftVal.number) {
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -587,14 +598,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// right value is a constant, see if we can optimize
|
||||
val leftValue: Expression = expr2.left
|
||||
val rightConst: NumericLiteralValue = rightVal2
|
||||
when (val cv = rightConst.number.toDouble()) {
|
||||
when (val cv = rightConst.number) {
|
||||
-1.0 -> {
|
||||
// -left
|
||||
return PrefixExpression("-", leftValue, expr.position)
|
||||
}
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteralValue(rightConst.type, 0, expr.position)
|
||||
return NumericLiteralValue(rightConst.type, 0.0, expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
// left
|
||||
@ -635,12 +646,12 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
return NumericLiteralValue(targetDt, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
return NumericLiteralValue(targetDt, 0.0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
@ -687,7 +698,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
// mkword(0, msb(v))
|
||||
val zero = NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
|
||||
}
|
||||
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
||||
|
@ -2,6 +2,9 @@ package prog8.optimizer
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
@ -65,3 +68,15 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
|
||||
return when {
|
||||
// TODO assume (hope) cx16.r9 isn't used for anything else...
|
||||
dt.istype(DataType.UBYTE) -> listOf("cx16", "r9L")
|
||||
dt.istype(DataType.BYTE) -> listOf("cx16", "r9sL")
|
||||
dt.istype(DataType.UWORD) -> listOf("cx16", "r9")
|
||||
dt.istype(DataType.WORD) -> listOf("cx16", "r9s")
|
||||
dt.isPassByReference -> listOf("cx16", "r9")
|
||||
else -> throw FatalAstException("invalid dt $dt")
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
@ -15,8 +12,6 @@ import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.size
|
||||
import kotlin.math.floor
|
||||
|
||||
internal const val retvarName = "prog8_retval"
|
||||
|
||||
|
||||
class StatementOptimizer(private val program: Program,
|
||||
private val errors: IErrorReporter,
|
||||
@ -24,33 +19,22 @@ class StatementOptimizer(private val program: Program,
|
||||
private val compTarget: ICompilationTarget
|
||||
) : AstWalker() {
|
||||
|
||||
private val subsThatNeedReturnVariable = mutableSetOf<Triple<IStatementContainer, DataType, Position>>()
|
||||
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
for(returnvar in subsThatNeedReturnVariable) {
|
||||
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null,
|
||||
isArray = false,
|
||||
autogeneratedDontRemove = true,
|
||||
sharedWithAsm = false,
|
||||
position = returnvar.third
|
||||
)
|
||||
returnvar.first.statements.add(0, decl)
|
||||
}
|
||||
subsThatNeedReturnVariable.clear()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with a simple value,
|
||||
// remove the jump altogeter and inline the returnvalue directly.
|
||||
|
||||
fun scopePrefix(variable: IdentifierReference): IdentifierReference {
|
||||
val target = variable.targetStatement(program) as INamedStatement
|
||||
return IdentifierReference(target.scopedName, variable.position)
|
||||
}
|
||||
|
||||
val subroutine = functionCall.target.targetSubroutine(program)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return && first.value?.isSimple==true) {
|
||||
val copy = when(val orig = first.value!!) {
|
||||
is AddressOf -> {
|
||||
val scoped = scopePrefix(orig.identifier, subroutine)
|
||||
val scoped = scopePrefix(orig.identifier)
|
||||
AddressOf(scoped, orig.position)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -59,7 +43,7 @@ class StatementOptimizer(private val program: Program,
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> scopePrefix(orig, subroutine)
|
||||
is IdentifierReference -> scopePrefix(orig)
|
||||
is NumericLiteralValue -> orig.copy()
|
||||
is StringLiteralValue -> orig.copy()
|
||||
else -> return noModifications
|
||||
@ -70,13 +54,10 @@ class StatementOptimizer(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun scopePrefix(variable: IdentifierReference, subroutine: Subroutine): IdentifierReference {
|
||||
val scoped = subroutine.makeScopedName(variable.nameInSource.last())
|
||||
return IdentifierReference(scoped.split('.'), variable.position)
|
||||
}
|
||||
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
|
||||
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionStatementPlaceholder) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in functions.purefunctionNames) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
@ -101,7 +82,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
@ -109,12 +90,12 @@ class StatementOptimizer(private val program: Program,
|
||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val chrout2 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(
|
||||
@ -134,23 +115,25 @@ class StatementOptimizer(private val program: Program,
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
// see if we can optimize any complex arguments
|
||||
// TODO for now, only works for single-argument functions because we use just 1 temp var: R9
|
||||
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
|
||||
val arg = functionCallStatement.args[0]
|
||||
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
|
||||
val name = getTempVarName(arg.inferType(program))
|
||||
val tempvar = IdentifierReference(name, functionCallStatement.position)
|
||||
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, functionCallStatement.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// // if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
|
||||
// val subroutine = functionCall.target.targetSubroutine(program)
|
||||
// if(subroutine!=null) {
|
||||
// val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
// if(first is Return && first.value!=null) {
|
||||
// val constval = first.value?.constValue(program)
|
||||
// if(constval!=null)
|
||||
// return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty if statements
|
||||
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
|
||||
@ -215,7 +198,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(size==1) {
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
|
||||
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
|
||||
val byte = NumericLiteralValue(DataType.UBYTE, character.toDouble(), iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
@ -296,12 +279,13 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.parent as IStatementContainer
|
||||
val label = jump.identifier?.targetStatement(program)
|
||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||
return listOf(IAstModification.Remove(jump, scope))
|
||||
|
||||
if(!jump.isGosub) {
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.parent as IStatementContainer
|
||||
val label = jump.identifier?.targetStatement(program)
|
||||
if (label != null && scope.statements.indexOf(label) == scope.statements.indexOf(jump) + 1)
|
||||
return listOf(IAstModification.Remove(jump, scope))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -324,22 +308,22 @@ class StatementOptimizer(private val program: Program,
|
||||
if(rNum!=null) {
|
||||
if (op1 == "+" || op1 == "-") {
|
||||
if (op2 == "+") {
|
||||
// A = A +/- B + N
|
||||
// A = A +/- B + N ---> A = A +/- B ; A = A + N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val addConstant = Assignment(
|
||||
assignment.target,
|
||||
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N
|
||||
// A = A +/- B - N ---> A = A +/- B ; A = A - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val subConstant = Assignment(
|
||||
assignment.target,
|
||||
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
)
|
||||
return listOf(
|
||||
@ -374,14 +358,26 @@ class StatementOptimizer(private val program: Program,
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
|
||||
// optimize binary expressions a bit
|
||||
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
val rightCv = bexpr.right.constValue(program)?.number
|
||||
if(bexpr.operator=="-" && rightCv==null) {
|
||||
if(bexpr.right isSameAs assignment.target) {
|
||||
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
|
||||
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
|
||||
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), assignment.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(bexpr, negation, assignment),
|
||||
IAstModification.InsertAfter(assignment, addValue, parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (rightCv != null && assignment.target isSameAs bexpr.left) {
|
||||
// assignments of the form: X = X <operator> <expr>
|
||||
// remove assignments that have no effect (such as X=X+0)
|
||||
// optimize/rewrite some other expressions
|
||||
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
@ -394,7 +390,7 @@ class StatementOptimizer(private val program: Program,
|
||||
repeat(rightCv.toInt()) {
|
||||
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||
listOf(IAstModification.ReplaceNode(assignment, if(incs.statements.size==1) incs.statements[0] else incs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -439,12 +435,17 @@ class StatementOptimizer(private val program: Program,
|
||||
val returnDt = subr.returntypes.single()
|
||||
if (returnDt in IntegerDatatypes) {
|
||||
// first assign to intermediary variable, then return that
|
||||
subsThatNeedReturnVariable.add(Triple(subr, returnDt, returnStmt.position))
|
||||
val returnValueIntermediary1 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||
val returnValueIntermediary2 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary1, null, null, returnStmt.position)
|
||||
val returnVarName = "retval_interm_" + when(returnDt) {
|
||||
DataType.UBYTE -> "ub"
|
||||
DataType.BYTE -> "b"
|
||||
DataType.UWORD -> "uw"
|
||||
DataType.WORD -> "w"
|
||||
else -> "<undefined>"
|
||||
}
|
||||
val returnValueIntermediary = IdentifierReference(listOf("prog8_lib", returnVarName), returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
||||
|
@ -1,18 +1,16 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.CallGraph
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.isInRegularRAMof
|
||||
import prog8.compilerinterface.isIOAddress
|
||||
|
||||
|
||||
class UnusedCodeRemover(private val program: Program,
|
||||
@ -30,27 +28,28 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(breakStmt, parent as IStatementContainer)
|
||||
reportUnreachable(breakStmt)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(jump, parent as IStatementContainer)
|
||||
if(!jump.isGosub)
|
||||
reportUnreachable(jump)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(returnStmt, parent as IStatementContainer)
|
||||
reportUnreachable(returnStmt)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.last() == "exit")
|
||||
reportUnreachable(functionCallStatement, parent as IStatementContainer)
|
||||
reportUnreachable(functionCallStatement)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun reportUnreachable(stmt: Statement, parent: IStatementContainer) {
|
||||
private fun reportUnreachable(stmt: Statement) {
|
||||
when(val next = stmt.nextSibling()) {
|
||||
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
|
||||
else -> errors.warn("unreachable code", next.position)
|
||||
@ -58,8 +57,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
|
||||
return deduplicateAssignments(scope.statements, scope)
|
||||
}
|
||||
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
@ -75,8 +73,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val removeDoubleAssignments = deduplicateAssignments(block.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
|
||||
return deduplicateAssignments(block.statements, block)
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
@ -99,17 +96,34 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
|
||||
return deduplicateAssignments(subroutine.statements, subroutine)
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.type==VarDeclType.VAR) {
|
||||
val forceOutput = "force_output" in decl.definingBlock.options()
|
||||
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
|
||||
if (callgraph.unused(decl)) {
|
||||
val usages = callgraph.usages(decl)
|
||||
if (usages.isEmpty()) {
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
|
||||
} else {
|
||||
// if all usages are just an assignment to this vardecl,
|
||||
// and it is in regular RAM, then remove the var as well including all assignments
|
||||
val assignTargets = usages.mapNotNull {
|
||||
it.parent as? AssignTarget
|
||||
}.filter {
|
||||
!it.isIOAddress(compTarget.machine)
|
||||
}
|
||||
if(assignTargets.size==usages.size) {
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
val assignmentsToRemove = assignTargets.map { it.parent to it.parent.parent as IStatementContainer}.toSet()
|
||||
return assignmentsToRemove.map {
|
||||
IAstModification.Remove(it.first, it.second)
|
||||
} + listOf(
|
||||
IAstModification.Remove(decl, parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,28 +131,117 @@ class UnusedCodeRemover(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
|
||||
private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> {
|
||||
// removes 'duplicate' assignments that assign the same target directly after another
|
||||
val linesToRemove = mutableListOf<Assignment>()
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
for (stmtPairs in statements.windowed(2, step = 1)) {
|
||||
val assign1 = stmtPairs[0] as? Assignment
|
||||
val assign2 = stmtPairs[1] as? Assignment
|
||||
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
|
||||
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAMof(compTarget.machine)) {
|
||||
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
|
||||
// only remove the second assignment if its value is a simple expression!
|
||||
when(assign2.value) {
|
||||
is PrefixExpression,
|
||||
is BinaryExpression,
|
||||
is TypecastExpression,
|
||||
is FunctionCall -> { /* don't remove */ }
|
||||
else -> linesToRemove.add(assign1)
|
||||
}
|
||||
fun substituteZeroInBinexpr(expr: BinaryExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
|
||||
if(expr.left isSameAs assign2.target) {
|
||||
// X = X <oper> Right
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
expr.left, zero, expr
|
||||
))
|
||||
}
|
||||
if(expr.right isSameAs assign2.target) {
|
||||
// X = Left <oper> X
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
expr.right, zero, expr
|
||||
))
|
||||
}
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
if(leftBinExpr!=null && rightBinExpr==null) {
|
||||
if(leftBinExpr.left isSameAs assign2.target) {
|
||||
// X = (X <oper> Right) <oper> Something
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
leftBinExpr.left, zero, leftBinExpr
|
||||
))
|
||||
}
|
||||
if(leftBinExpr.right isSameAs assign2.target) {
|
||||
// X = (Left <oper> X) <oper> Something
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
leftBinExpr.right, zero, leftBinExpr
|
||||
))
|
||||
}
|
||||
}
|
||||
if(leftBinExpr==null && rightBinExpr!=null) {
|
||||
if(rightBinExpr.left isSameAs assign2.target) {
|
||||
// X = Something <oper> (X <oper> Right)
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
rightBinExpr.left, zero, rightBinExpr
|
||||
))
|
||||
}
|
||||
if(rightBinExpr.right isSameAs assign2.target) {
|
||||
// X = Something <oper> (Left <oper> X)
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
rightBinExpr.right, zero, rightBinExpr
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return linesToRemove
|
||||
fun substituteZeroInPrefixexpr(expr: PrefixExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
|
||||
if(expr.expression isSameAs assign2.target) {
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
expr.expression, zero, expr
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fun substituteZeroInTypecast(expr: TypecastExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
|
||||
if(expr.expression isSameAs assign2.target) {
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
expr.expression, zero, expr
|
||||
))
|
||||
}
|
||||
val subCast = expr.expression as? TypecastExpression
|
||||
if(subCast!=null && subCast.expression isSameAs assign2.target) {
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
subCast.expression, zero, subCast
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
for (stmtPairs in statements.windowed(2, step = 1)) {
|
||||
val assign1 = stmtPairs[0] as? Assignment
|
||||
val assign2 = stmtPairs[1] as? Assignment
|
||||
if (assign1 != null && assign2 != null) {
|
||||
val cvalue1 = assign1.value.constValue(program)
|
||||
if(cvalue1!=null && cvalue1.number==0.0 && assign2.target.isSameAs(assign1.target, program) && assign2.isAugmentable) {
|
||||
val value2 = assign2.value
|
||||
val zero = VarDecl.defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position)
|
||||
when(value2) {
|
||||
is BinaryExpression -> substituteZeroInBinexpr(value2, zero, assign1, assign2)
|
||||
is PrefixExpression -> substituteZeroInPrefixexpr(value2, zero, assign1, assign2)
|
||||
is TypecastExpression -> substituteZeroInTypecast(value2, zero, assign1, assign2)
|
||||
else -> {}
|
||||
}
|
||||
} else {
|
||||
if (assign1.target.isSameAs(assign2.target, program) && !assign1.target.isIOAddress(compTarget.machine)) {
|
||||
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(assign2.target.identifier!!.nameInSource))
|
||||
// only remove the second assignment if its value is a simple expression!
|
||||
when(assign2.value) {
|
||||
is PrefixExpression,
|
||||
is BinaryExpression,
|
||||
is TypecastExpression,
|
||||
is FunctionCall -> { /* don't remove */ }
|
||||
else -> linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modifications + linesToRemove.map { IAstModification.Remove(it, scope) }
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ plugins {
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
||||
id "io.kotest" version "0.3.8"
|
||||
}
|
||||
|
||||
java {
|
||||
@ -23,10 +24,7 @@ dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
|
@ -1,10 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="Python" name="Python">
|
||||
<configuration sdkName="Python 3.9" />
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
@ -17,13 +12,12 @@
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
|
||||
<orderEntry type="library" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeOptimizers" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
<orderEntry type="module" module-name="codeGeneration" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -12,7 +12,7 @@ floats {
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
ubyte[5] tempvar_swap_float ; used for some swap() operations
|
||||
float tempvar_swap_float ; used for some swap() operations
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
|
||||
|
@ -33,6 +33,9 @@ graphics {
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
; TODO implement this as optimized assembly, for instance https://github.com/EgonOlsen71/bresenham/blob/main/src/asm/graphics.asm ??
|
||||
; or from here https://retro64.altervista.org/blog/an-introduction-to-vector-based-graphics-the-commodore-64-rotating-simple-3d-objects/
|
||||
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
swap(x1, x2)
|
||||
|
@ -597,62 +597,34 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
inline asmsub rsave() {
|
||||
; save cpu status flag and all registers A, X, Y.
|
||||
; see http://6502.org/tutorials/register_preservation.html
|
||||
%asm {{
|
||||
php
|
||||
sta P8ZP_SCRATCH_REG
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda P8ZP_SCRATCH_REG
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rrestore() {
|
||||
; restore all registers and cpu status flag
|
||||
%asm {{
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
@ -699,6 +671,23 @@ cx16 {
|
||||
&uword r14 = $cf1c
|
||||
&uword r15 = $cf1e
|
||||
|
||||
&word r0s = $cf00
|
||||
&word r1s = $cf02
|
||||
&word r2s = $cf04
|
||||
&word r3s = $cf06
|
||||
&word r4s = $cf08
|
||||
&word r5s = $cf0a
|
||||
&word r6s = $cf0c
|
||||
&word r7s = $cf0e
|
||||
&word r8s = $cf10
|
||||
&word r9s = $cf12
|
||||
&word r10s = $cf14
|
||||
&word r11s = $cf16
|
||||
&word r12s = $cf18
|
||||
&word r13s = $cf1a
|
||||
&word r14s = $cf1c
|
||||
&word r15s = $cf1e
|
||||
|
||||
&ubyte r0L = $cf00
|
||||
&ubyte r1L = $cf02
|
||||
&ubyte r2L = $cf04
|
||||
@ -732,4 +721,38 @@ cx16 {
|
||||
&ubyte r13H = $cf1b
|
||||
&ubyte r14H = $cf1d
|
||||
&ubyte r15H = $cf1f
|
||||
|
||||
&byte r0sL = $cf00
|
||||
&byte r1sL = $cf02
|
||||
&byte r2sL = $cf04
|
||||
&byte r3sL = $cf06
|
||||
&byte r4sL = $cf08
|
||||
&byte r5sL = $cf0a
|
||||
&byte r6sL = $cf0c
|
||||
&byte r7sL = $cf0e
|
||||
&byte r8sL = $cf10
|
||||
&byte r9sL = $cf12
|
||||
&byte r10sL = $cf14
|
||||
&byte r11sL = $cf16
|
||||
&byte r12sL = $cf18
|
||||
&byte r13sL = $cf1a
|
||||
&byte r14sL = $cf1c
|
||||
&byte r15sL = $cf1e
|
||||
|
||||
&byte r0sH = $cf01
|
||||
&byte r1sH = $cf03
|
||||
&byte r2sH = $cf05
|
||||
&byte r3sH = $cf07
|
||||
&byte r4sH = $cf09
|
||||
&byte r5sH = $cf0b
|
||||
&byte r6sH = $cf0d
|
||||
&byte r7sH = $cf0f
|
||||
&byte r8sH = $cf11
|
||||
&byte r9sH = $cf13
|
||||
&byte r10sH = $cf15
|
||||
&byte r11sH = $cf17
|
||||
&byte r12sH = $cf19
|
||||
&byte r13sH = $cf1b
|
||||
&byte r14sH = $cf1d
|
||||
&byte r15sH = $cf1f
|
||||
}
|
||||
|
@ -12,14 +12,14 @@ conv {
|
||||
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
sty string_out
|
||||
sta string_out+1
|
||||
stx string_out+2
|
||||
lda #0
|
||||
sta string_out+3
|
||||
plx
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -27,7 +27,7 @@ asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the ubyte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
jsr conv.ubyte2decimal
|
||||
@ -53,7 +53,7 @@ _output_byte_digits
|
||||
iny
|
||||
lda #0
|
||||
sta string_out,y
|
||||
plx
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -61,7 +61,7 @@ _output_byte_digits
|
||||
asmsub str_b (byte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the byte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
cmp #0
|
||||
@ -149,7 +149,7 @@ asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
@ -157,7 +157,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ plx
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -165,7 +165,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldx #0
|
||||
_output_digits
|
||||
@ -183,7 +183,7 @@ _gotdigit sta string_out,x
|
||||
bne _gotdigit
|
||||
_end lda #0
|
||||
sta string_out,x
|
||||
plx
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
_allzero lda #'0'
|
||||
@ -198,7 +198,7 @@ asmsub str_w (word value @ AY) clobbers(A,Y) {
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl str_uw
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
pha
|
||||
lda #'-'
|
||||
sta string_out
|
||||
@ -516,7 +516,7 @@ asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
|
||||
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||
;By Omegamatrix Further optimizations by tepples
|
||||
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
|
||||
; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15
|
||||
|
||||
;HexToDec99
|
||||
; start in A
|
||||
|
@ -13,7 +13,7 @@ floats {
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
ubyte[5] tempvar_swap_float ; used for some swap() operations
|
||||
float tempvar_swap_float ; used for some swap() operations
|
||||
|
||||
|
||||
; ---- ROM float functions ----
|
||||
|
@ -775,6 +775,7 @@ _done
|
||||
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
||||
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 ! TODO allow per-pixel horizontal positioning
|
||||
; TODO draw whole horizontal spans using vera auto increment if possible, instead of per-character columns
|
||||
uword chardataptr
|
||||
when active_mode {
|
||||
1, 5 -> {
|
||||
@ -808,11 +809,8 @@ _done
|
||||
sta cx16.VERA_ADDR_L
|
||||
bcc +
|
||||
inc cx16.VERA_ADDR_M
|
||||
+ lda x
|
||||
clc
|
||||
adc #1
|
||||
sta x
|
||||
bcc +
|
||||
+ inc x
|
||||
bne +
|
||||
inc x+1
|
||||
+ dey
|
||||
bne -
|
||||
@ -826,7 +824,6 @@ _done
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||
repeat 8 {
|
||||
; TODO rewrite this inner loop fully in assembly
|
||||
position(x,y)
|
||||
y++
|
||||
%asm {{
|
||||
@ -855,7 +852,9 @@ _done
|
||||
while @(sctextptr) {
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
repeat 8 {
|
||||
; TODO rewrite this inner loop fully in assembly
|
||||
; TODO rewrite this inner loop partly in assembly
|
||||
; requires expanding the charbits to 2-bits per pixel (based on color)
|
||||
; also it's way more efficient to draw whole horizontal spans instead of per-character
|
||||
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
|
||||
repeat 8 {
|
||||
charbits <<= 1
|
||||
|
@ -95,7 +95,7 @@ cx16 {
|
||||
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
|
||||
; the sixteen virtual 16-bit registers
|
||||
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
|
||||
&uword r0 = $0002
|
||||
&uword r1 = $0004
|
||||
&uword r2 = $0006
|
||||
@ -113,6 +113,23 @@ cx16 {
|
||||
&uword r14 = $001e
|
||||
&uword r15 = $0020
|
||||
|
||||
&word r0s = $0002
|
||||
&word r1s = $0004
|
||||
&word r2s = $0006
|
||||
&word r3s = $0008
|
||||
&word r4s = $000a
|
||||
&word r5s = $000c
|
||||
&word r6s = $000e
|
||||
&word r7s = $0010
|
||||
&word r8s = $0012
|
||||
&word r9s = $0014
|
||||
&word r10s = $0016
|
||||
&word r11s = $0018
|
||||
&word r12s = $001a
|
||||
&word r13s = $001c
|
||||
&word r14s = $001e
|
||||
&word r15s = $0020
|
||||
|
||||
&ubyte r0L = $0002
|
||||
&ubyte r1L = $0004
|
||||
&ubyte r2L = $0006
|
||||
@ -147,6 +164,39 @@ cx16 {
|
||||
&ubyte r14H = $001f
|
||||
&ubyte r15H = $0021
|
||||
|
||||
&byte r0sL = $0002
|
||||
&byte r1sL = $0004
|
||||
&byte r2sL = $0006
|
||||
&byte r3sL = $0008
|
||||
&byte r4sL = $000a
|
||||
&byte r5sL = $000c
|
||||
&byte r6sL = $000e
|
||||
&byte r7sL = $0010
|
||||
&byte r8sL = $0012
|
||||
&byte r9sL = $0014
|
||||
&byte r10sL = $0016
|
||||
&byte r11sL = $0018
|
||||
&byte r12sL = $001a
|
||||
&byte r13sL = $001c
|
||||
&byte r14sL = $001e
|
||||
&byte r15sL = $0020
|
||||
|
||||
&byte r0sH = $0003
|
||||
&byte r1sH = $0005
|
||||
&byte r2sH = $0007
|
||||
&byte r3sH = $0009
|
||||
&byte r4sH = $000b
|
||||
&byte r5sH = $000d
|
||||
&byte r6sH = $000f
|
||||
&byte r7sH = $0011
|
||||
&byte r8sH = $0013
|
||||
&byte r9sH = $0015
|
||||
&byte r10sH = $0017
|
||||
&byte r11sH = $0019
|
||||
&byte r12sH = $001b
|
||||
&byte r13sH = $001d
|
||||
&byte r14sH = $001f
|
||||
&byte r15sH = $0021
|
||||
|
||||
; VERA registers
|
||||
|
||||
@ -262,7 +312,7 @@ romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||
|
||||
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
|
||||
; TODO specify the correct clobbers for all functions below, we now assume all 3 regs are clobbered
|
||||
|
||||
; high level graphics & fonts
|
||||
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||
@ -317,14 +367,14 @@ romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
|
||||
; ---- utilities -----
|
||||
|
||||
inline asmsub rombank(ubyte rombank @A) {
|
||||
inline asmsub rombank(ubyte bank @A) {
|
||||
; -- set the rom banks
|
||||
%asm {{
|
||||
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rambank(ubyte rambank @A) {
|
||||
inline asmsub rambank(ubyte bank @A) {
|
||||
; -- set the ram bank
|
||||
%asm {{
|
||||
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
|
||||
@ -809,27 +859,6 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rsave() {
|
||||
; save cpu status flag and all registers A, X, Y.
|
||||
; see http://6502.org/tutorials/register_preservation.html
|
||||
%asm {{
|
||||
php
|
||||
pha
|
||||
phy
|
||||
phx
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rrestore() {
|
||||
; restore all registers and cpu status flag
|
||||
%asm {{
|
||||
plx
|
||||
ply
|
||||
pla
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
@ -839,25 +868,25 @@ sys {
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -565,9 +565,8 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
ina
|
||||
bne +
|
||||
iny
|
||||
+ bra print_uw
|
||||
}}
|
||||
|
@ -91,6 +91,13 @@ func_sin8_into_A .proc
|
||||
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
.pend
|
||||
|
||||
func_sinr8_into_A .proc
|
||||
tay
|
||||
lda _sinecosR8,y
|
||||
rts
|
||||
_sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
.pend
|
||||
|
||||
func_sin8u_into_A .proc
|
||||
tay
|
||||
lda _sinecos8u,y
|
||||
@ -98,6 +105,13 @@ func_sin8u_into_A .proc
|
||||
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
.pend
|
||||
|
||||
func_sinr8u_into_A .proc
|
||||
tay
|
||||
lda _sinecosR8u,y
|
||||
rts
|
||||
_sinecosR8u .byte trunc(128.0 + 127.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
.pend
|
||||
|
||||
func_sin8_stack .proc
|
||||
tay
|
||||
lda func_sin8_into_A._sinecos8,y
|
||||
@ -106,6 +120,14 @@ func_sin8_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr8_stack .proc
|
||||
tay
|
||||
lda func_sinr8_into_A._sinecosR8,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin8u_stack .proc
|
||||
tay
|
||||
lda func_sin8u_into_A._sinecos8u,y
|
||||
@ -114,18 +136,38 @@ func_sin8u_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr8u_stack .proc
|
||||
tay
|
||||
lda func_sinr8u_into_A._sinecosR8u,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8_into_A .proc
|
||||
tay
|
||||
lda func_sin8_into_A._sinecos8+64,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8_into_A .proc
|
||||
tay
|
||||
lda func_sinr8_into_A._sinecosR8+45,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8u_into_A .proc
|
||||
tay
|
||||
lda func_sin8u_into_A._sinecos8u+64,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8u_into_A .proc
|
||||
tay
|
||||
lda func_sinr8u_into_A._sinecosR8u+45,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8_stack .proc
|
||||
tay
|
||||
lda func_sin8_into_A._sinecos8+64,y
|
||||
@ -134,6 +176,14 @@ func_cos8_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8_stack .proc
|
||||
tay
|
||||
lda func_sinr8_into_A._sinecosR8+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8u_stack .proc
|
||||
tay
|
||||
lda func_sin8u_into_A._sinecos8u+64,y
|
||||
@ -142,6 +192,14 @@ func_cos8u_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8u_stack .proc
|
||||
tay
|
||||
lda func_sinr8u_into_A._sinecosR8u+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin16_into_AY .proc
|
||||
tay
|
||||
lda _sinecos8lo,y
|
||||
@ -155,6 +213,19 @@ _sinecos8lo .byte <_
|
||||
_sinecos8hi .byte >_
|
||||
.pend
|
||||
|
||||
func_sinr16_into_AY .proc
|
||||
tay
|
||||
lda _sinecosR8lo,y
|
||||
pha
|
||||
lda _sinecosR8hi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32767.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
_sinecosR8lo .byte <_
|
||||
_sinecosR8hi .byte >_
|
||||
.pend
|
||||
|
||||
func_sin16u_into_AY .proc
|
||||
tay
|
||||
lda _sinecos8ulo,y
|
||||
@ -168,6 +239,18 @@ _sinecos8ulo .byte <_
|
||||
_sinecos8uhi .byte >_
|
||||
.pend
|
||||
|
||||
func_sinr16u_into_AY .proc
|
||||
tay
|
||||
lda _sinecosR8ulo,y
|
||||
pha
|
||||
lda _sinecosR8uhi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32768.0 + 32767.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
_sinecosR8ulo .byte <_
|
||||
_sinecosR8uhi .byte >_
|
||||
.pend
|
||||
|
||||
func_sin16_stack .proc
|
||||
tay
|
||||
@ -179,6 +262,16 @@ func_sin16_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr16_stack .proc
|
||||
tay
|
||||
lda func_sinr16_into_AY._sinecosR8lo,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16_into_AY._sinecosR8hi,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin16u_stack .proc
|
||||
tay
|
||||
lda func_sin16u_into_AY._sinecos8ulo,y
|
||||
@ -189,6 +282,16 @@ func_sin16u_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr16u_stack .proc
|
||||
tay
|
||||
lda func_sinr16u_into_AY._sinecosR8ulo,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16u_into_AY._sinecosR8uhi,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16_into_AY .proc
|
||||
tay
|
||||
lda func_sin16_into_AY._sinecos8lo+64,y
|
||||
@ -199,6 +302,16 @@ func_cos16_into_AY .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16_into_AY .proc
|
||||
tay
|
||||
lda func_sinr16_into_AY._sinecosR8lo+45,y
|
||||
pha
|
||||
lda func_sinr16_into_AY._sinecosR8hi+45,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16u_into_AY .proc
|
||||
tay
|
||||
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
||||
@ -209,6 +322,16 @@ func_cos16u_into_AY .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16u_into_AY .proc
|
||||
tay
|
||||
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
|
||||
pha
|
||||
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16_stack .proc
|
||||
tay
|
||||
lda func_sin16_into_AY._sinecos8lo+64,y
|
||||
@ -219,6 +342,16 @@ func_cos16_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16_stack .proc
|
||||
tay
|
||||
lda func_sinr16_into_AY._sinecosR8lo+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16_into_AY._sinecosR8hi+45,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16u_stack .proc
|
||||
tay
|
||||
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
||||
@ -229,6 +362,16 @@ func_cos16u_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16u_stack .proc
|
||||
tay
|
||||
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
abs_b_stack .proc
|
||||
; -- push abs(A) on stack (as byte)
|
||||
jsr abs_b_into_A
|
||||
|
@ -6,12 +6,24 @@ prog8_lib {
|
||||
%asminclude "library:prog8_lib.asm"
|
||||
%asminclude "library:prog8_funcs.asm"
|
||||
|
||||
; TODO these retval variables are no longer used???
|
||||
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
; NOTE: these variables are checked in the StatementReorderer (in fun after(decl: VarDecl)), for these exact names!
|
||||
; to store intermediary expression results for return values:
|
||||
; NOTE: these variables are used in the StatementReorderer and StatementOptimizer
|
||||
uword @zp retval_interm_uw
|
||||
word @zp retval_interm_w
|
||||
ubyte @zp retval_interm_ub
|
||||
byte @zp retval_interm_b
|
||||
word retval_interm_w2
|
||||
byte retval_interm_b2
|
||||
|
||||
; prog8 "hooks" to be able to access the temporary scratch variables
|
||||
; YOU SHOULD NOT USE THESE IN USER CODE - THESE ARE MEANT FOR INTERNAL COMPILER USE
|
||||
; NOTE: the assembly code generator will match these names and not generate
|
||||
; new variables/memdefs for them, rather, they'll point to the scratch variables directly.
|
||||
&ubyte P8ZP_SCRATCH_REG = $ff
|
||||
&byte P8ZP_SCRATCH_B1 = $ff
|
||||
&uword P8ZP_SCRATCH_W1 = $ff
|
||||
&word P8ZP_SCRATCH_W2 = $ff
|
||||
|
||||
|
||||
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
|
@ -1 +1 @@
|
||||
7.2
|
||||
7.4
|
||||
|
@ -3,6 +3,7 @@ package prog8
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
@ -66,6 +67,11 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
if(srcdirs.firstOrNull()!=".")
|
||||
srcdirs.add(0, ".")
|
||||
|
||||
if (compilationTarget != C64Target.name && compilationTarget != Cx16Target.name) {
|
||||
System.err.println("Invalid compilation target: $compilationTarget")
|
||||
return false
|
||||
}
|
||||
|
||||
if(watchMode==true) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
val allImportedFiles = mutableSetOf<Path>()
|
||||
@ -75,10 +81,18 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult = compileProgram(filepath,
|
||||
dontOptimize!=true, optimizeFloatExpressions==true,
|
||||
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
|
||||
compilationTarget, srcdirs, outputPath)
|
||||
val args = CompilerArguments(
|
||||
filepath,
|
||||
dontOptimize != true,
|
||||
optimizeFloatExpressions == true,
|
||||
dontWriteAssembly != true,
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
compilationTarget,
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
val compilationResult = compileProgram(args)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
@ -115,11 +129,19 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
try {
|
||||
compilationResult = compileProgram(filepath,
|
||||
dontOptimize!=true, optimizeFloatExpressions==true,
|
||||
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
|
||||
compilationTarget, srcdirs, outputPath)
|
||||
if(!compilationResult.success)
|
||||
val args = CompilerArguments(
|
||||
filepath,
|
||||
dontOptimize != true,
|
||||
optimizeFloatExpressions == true,
|
||||
dontWriteAssembly != true,
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
compilationTarget,
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
compilationResult = compileProgram(args)
|
||||
if (!compilationResult.success)
|
||||
return false
|
||||
} catch (x: AstException) {
|
||||
return false
|
||||
|
@ -11,17 +11,30 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.astprocessing.isSubroutineParameter
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.optimizer.getTempVarName
|
||||
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
private val subroutineVariables = mutableMapOf<Subroutine, MutableList<Pair<String, VarDecl>>>()
|
||||
|
||||
private fun rememberSubroutineVar(decl: VarDecl) {
|
||||
val sub = decl.definingSubroutine ?: return
|
||||
var varsList = subroutineVariables[sub]
|
||||
if(varsList==null) {
|
||||
varsList = mutableListOf()
|
||||
subroutineVariables[sub] = varsList
|
||||
}
|
||||
varsList.add(decl.name to decl)
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
|
||||
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
|
||||
|
||||
subroutineVariables.add(decl.name to decl)
|
||||
rememberSubroutineVar(decl)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -31,7 +44,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& assignment.target.isInRegularRAMof(options.compTarget.machine)) {
|
||||
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
|
||||
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
|
||||
@ -39,20 +52,24 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
|
||||
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||
if (binExpr.left !is BinaryExpression) {
|
||||
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
|
||||
if (binExpr.right.referencesIdentifier(assignment.target.identifier!!.nameInSource)) {
|
||||
// the right part of the expression contains the target variable itself.
|
||||
// we can't 'split' it trivially because the variable will be changed halfway through.
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||
// use the other part of the expression to split.
|
||||
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
|
||||
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), sourceDt, implicit=true)
|
||||
val assignRight = Assignment(assignment.target, right, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
} else {
|
||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
|
||||
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), sourceDt, implicit=true)
|
||||
val assignLeft = Assignment(assignment.target, left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
@ -63,47 +80,16 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
|
||||
private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>()
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.clear()
|
||||
addedIfConditionVars.clear()
|
||||
|
||||
if(!subroutine.isAsmSubroutine) {
|
||||
// change 'str' parameters into 'uword' (just treat it as an address)
|
||||
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
|
||||
val parameterChanges = stringParams.map {
|
||||
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
|
||||
IAstModification.ReplaceNode(it, uwordParam, subroutine)
|
||||
}
|
||||
|
||||
val stringParamNames = stringParams.map { it.name }.toSet()
|
||||
val varsChanges = subroutine.statements
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter { it.autogeneratedDontRemove && it.name in stringParamNames }
|
||||
.map {
|
||||
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, it.position)
|
||||
IAstModification.ReplaceNode(it, newvar, subroutine)
|
||||
}
|
||||
|
||||
return parameterChanges + varsChanges
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
|
||||
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
|
||||
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
|
||||
subroutineVariables.addAll(decls.map { it.name to it })
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val firstDeclarations = mutableMapOf<String, VarDecl>()
|
||||
for(decl in subroutineVariables) {
|
||||
val rememberedSubroutineVars = subroutineVariables.getOrDefault(subroutine, mutableListOf())
|
||||
for(decl in rememberedSubroutineVars) {
|
||||
val existing = firstDeclarations[decl.first]
|
||||
if(existing!=null && existing !== decl.second) {
|
||||
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
|
||||
@ -111,7 +97,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
firstDeclarations[decl.first] = decl.second
|
||||
}
|
||||
}
|
||||
|
||||
rememberedSubroutineVars.clear()
|
||||
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
@ -141,7 +127,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove superfluous typecasts (outside of expressions)
|
||||
// see if we can remove redundant typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword
|
||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
|
||||
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
|
||||
@ -201,12 +187,76 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||
}
|
||||
|
||||
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
|
||||
(binExpr.left as? NumericLiteralValue)?.number==0 &&
|
||||
(binExpr.right as? NumericLiteralValue)?.number!=0)
|
||||
throw InternalCompilerException("if 0==X should have been swapped to if X==0")
|
||||
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
|
||||
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
|
||||
throw FatalAstException("0==X should have been swapped to if X==0")
|
||||
|
||||
return noModifications
|
||||
// simplify the conditional expression, introduce simple assignments if required.
|
||||
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
|
||||
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
|
||||
val simplify = simplifyConditionalExpression(binExpr)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(simplify.rightVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(ifStatement, simplify.rightVarAssignment, parent as IStatementContainer)
|
||||
}
|
||||
if(simplify.leftVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(ifStatement, simplify.leftVarAssignment, parent as IStatementContainer)
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
private class CondExprSimplificationResult(
|
||||
val leftVarAssignment: Assignment?,
|
||||
val leftOperandReplacement: Expression?,
|
||||
val rightVarAssignment: Assignment?,
|
||||
val rightOperandReplacement: Expression?
|
||||
)
|
||||
|
||||
private fun simplifyConditionalExpression(expr: BinaryExpression): CondExprSimplificationResult {
|
||||
|
||||
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
|
||||
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
var leftOperandReplacement: Expression? = null
|
||||
var rightAssignment: Assignment? = null
|
||||
var rightOperandReplacement: Expression? = null
|
||||
|
||||
val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall
|
||||
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall
|
||||
|
||||
if(separateLeftExpr) {
|
||||
val name = getTempVarName(expr.left.inferType(program))
|
||||
leftOperandReplacement = IdentifierReference(name, expr.position)
|
||||
leftAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.left,
|
||||
expr.position
|
||||
)
|
||||
}
|
||||
if(separateRightExpr) {
|
||||
val dt = expr.right.inferType(program)
|
||||
val name = when {
|
||||
dt.istype(DataType.UBYTE) -> listOf("prog8_lib","retval_interm_ub")
|
||||
dt.istype(DataType.UWORD) -> listOf("prog8_lib","retval_interm_uw")
|
||||
dt.istype(DataType.BYTE) -> listOf("prog8_lib","retval_interm_b2")
|
||||
dt.istype(DataType.WORD) -> listOf("prog8_lib","retval_interm_w2")
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
rightOperandReplacement = IdentifierReference(name, expr.position)
|
||||
rightAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.right,
|
||||
expr.position
|
||||
)
|
||||
}
|
||||
return CondExprSimplificationResult(
|
||||
leftAssignment, leftOperandReplacement,
|
||||
rightAssignment, rightOperandReplacement
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
@ -224,7 +274,26 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||
}
|
||||
return noModifications
|
||||
|
||||
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
|
||||
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
|
||||
throw FatalAstException("0==X should have been swapped to if X==0")
|
||||
|
||||
// simplify the conditional expression, introduce simple assignments if required.
|
||||
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
|
||||
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
|
||||
val simplify = simplifyConditionalExpression(binExpr)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(simplify.rightVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertLast(simplify.rightVarAssignment, untilLoop.body)
|
||||
}
|
||||
if(simplify.leftVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertLast(simplify.leftVarAssignment, untilLoop.body)
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
@ -242,6 +311,18 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||
}
|
||||
|
||||
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
|
||||
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
|
||||
throw FatalAstException("0==X should have been swapped to if X==0")
|
||||
|
||||
// TODO simplify the conditional expression, introduce simple assignments if required.
|
||||
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
|
||||
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
|
||||
// NOTE: this is nasty for a while-statement as the condition occurs at the top of the loop
|
||||
// so the expression needs to be evaluated also before the loop is entered...
|
||||
// but I don't want to duplicate the expression.
|
||||
// val simplify = simplifyConditionalExpression(binExpr)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -255,13 +336,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes)
|
||||
return noModifications
|
||||
val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement))
|
||||
val (replaced, cast) = arg1.typecastTo(if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
|
||||
} else {
|
||||
if(dt2 in WordDatatypes)
|
||||
return noModifications
|
||||
val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement))
|
||||
val (replaced, cast) = arg2.typecastTo(if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -325,10 +408,10 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "r9L" else "r9"
|
||||
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "retval_interm_ub" else "retval_interm_b"
|
||||
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
|
||||
val target = AssignTarget(IdentifierReference(listOf("prog8_lib", register), expr.indexer.position), null, null, expr.indexer.position)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
|
||||
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
|
||||
|
@ -28,24 +28,27 @@ class CompilationResult(val success: Boolean,
|
||||
val compTarget: ICompilationTarget,
|
||||
val importedFiles: List<Path>)
|
||||
|
||||
class CompilerArguments(val filepath: Path,
|
||||
val optimize: Boolean,
|
||||
val optimizeFloatExpressions: Boolean,
|
||||
val writeAssembly: Boolean,
|
||||
val slowCodegenWarnings: Boolean,
|
||||
val quietAssembler: Boolean,
|
||||
val compilationTarget: String,
|
||||
val sourceDirs: List<String> = emptyList(),
|
||||
val outputDir: Path = Path(""),
|
||||
val errors: IErrorReporter = ErrorReporter())
|
||||
|
||||
// TODO refactor the gigantic list of parameters
|
||||
fun compileProgram(filepath: Path,
|
||||
optimize: Boolean,
|
||||
optimizeFloatExpressions: Boolean,
|
||||
writeAssembly: Boolean,
|
||||
slowCodegenWarnings: Boolean,
|
||||
quietAssembler: Boolean,
|
||||
compilationTarget: String,
|
||||
sourceDirs: List<String>,
|
||||
outputDir: Path,
|
||||
errors: IErrorReporter = ErrorReporter()): CompilationResult {
|
||||
|
||||
fun compileProgram(args: CompilerArguments): CompilationResult {
|
||||
var programName = ""
|
||||
lateinit var program: Program
|
||||
lateinit var importedFiles: List<Path>
|
||||
|
||||
val optimizeFloatExpr = if(args.optimize) args.optimizeFloatExpressions else false
|
||||
|
||||
val compTarget =
|
||||
when(compilationTarget) {
|
||||
when(args.compilationTarget) {
|
||||
C64Target.name -> C64Target
|
||||
Cx16Target.name -> Cx16Target
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
@ -54,30 +57,30 @@ fun compileProgram(filepath: Path,
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val (programresult, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
|
||||
val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
|
||||
with(compilationOptions) {
|
||||
this.slowCodegenWarnings = slowCodegenWarnings
|
||||
this.optimize = optimize
|
||||
this.optimizeFloatExpressions = optimizeFloatExpressions
|
||||
this.slowCodegenWarnings = args.slowCodegenWarnings
|
||||
this.optimize = args.optimize
|
||||
this.optimizeFloatExpressions = optimizeFloatExpr
|
||||
}
|
||||
program = programresult
|
||||
importedFiles = imported
|
||||
processAst(program, errors, compilationOptions)
|
||||
processAst(program, args.errors, compilationOptions)
|
||||
if (compilationOptions.optimize)
|
||||
optimizeAst(
|
||||
program,
|
||||
compilationOptions,
|
||||
errors,
|
||||
args.errors,
|
||||
BuiltinFunctionsFacade(BuiltinFunctions),
|
||||
compTarget
|
||||
)
|
||||
postprocessAst(program, errors, compilationOptions)
|
||||
postprocessAst(program, args.errors, compilationOptions)
|
||||
|
||||
// println("*********** AST BEFORE ASSEMBLYGEN *************")
|
||||
// printAst(program)
|
||||
// printProgram(program)
|
||||
|
||||
if (writeAssembly) {
|
||||
val result = writeAssembly(program, errors, outputDir, quietAssembler, compilationOptions)
|
||||
if (args.writeAssembly) {
|
||||
val result = writeAssembly(program, args.errors, args.outputDir, args.quietAssembler, compilationOptions)
|
||||
when (result) {
|
||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||
is WriteAssemblyResult.Fail -> {
|
||||
@ -253,16 +256,17 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// perform initial syntax checks and processings
|
||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||
program.preprocessAst()
|
||||
program.preprocessAst(program)
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
|
||||
// ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
|
||||
// By introduction a CHAR dt, we will also lose the opportunity to do constant-folding on any expression containing a char literal.
|
||||
// Yes this is different from strings that are only encoded in the code gen phase.
|
||||
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
|
||||
program.constantFold(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
program.reorderStatements(errors)
|
||||
program.reorderStatements(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.addTypecasts(errors)
|
||||
errors.report()
|
||||
@ -325,7 +329,7 @@ private fun writeAssembly(program: Program,
|
||||
errors.report()
|
||||
|
||||
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// printAst(program)
|
||||
// printProgram(program)
|
||||
|
||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = asmGeneratorFor(compilerOptions.compTarget,
|
||||
@ -348,7 +352,7 @@ private fun writeAssembly(program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
fun printAst(program: Program) {
|
||||
fun printProgram(program: Program) {
|
||||
println()
|
||||
val printer = AstToSourceTextConverter(::print, program)
|
||||
printer.visit(program)
|
||||
|
@ -51,7 +51,7 @@ class ModuleImporter(private val program: Program,
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
val import = Directive("%import", listOf(
|
||||
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
DirectiveArg("", name, 42u, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
), Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
return executeImportDirective(import, null)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ internal class AstChecker(private val program: Program,
|
||||
) : IAstVisitor {
|
||||
|
||||
override fun visit(program: Program) {
|
||||
assert(program === this.program)
|
||||
require(program === this.program)
|
||||
// there must be a single 'main' block with a 'start' subroutine for the program entry point.
|
||||
val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
|
||||
if(mainBlocks.size>1)
|
||||
@ -92,9 +92,9 @@ internal class AstChecker(private val program: Program,
|
||||
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
|
||||
if(range==null)
|
||||
return
|
||||
val step = range.step.constValue(program)?.number?.toDouble() ?: 1.0
|
||||
val step = range.step.constValue(program)?.number ?: 1.0
|
||||
if(step < -1.0) {
|
||||
val limit = range.to.constValue(program)?.number?.toDouble()
|
||||
val limit = range.to.constValue(program)?.number
|
||||
if(limit==0.0 && range.from.constValue(program)==null)
|
||||
errors.err("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping", forLoop.position)
|
||||
}
|
||||
@ -161,7 +161,6 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(forLoop)
|
||||
}
|
||||
|
||||
|
||||
override fun visit(jump: Jump) {
|
||||
val ident = jump.identifier
|
||||
if(ident!=null) {
|
||||
@ -170,17 +169,20 @@ internal class AstChecker(private val program: Program,
|
||||
if(targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
errors.err("can't jump to a builtin function", jump.position)
|
||||
}
|
||||
if(!jump.isGosub && targetStatement is Subroutine && targetStatement.parameters.any()) {
|
||||
errors.err("can't jump to a subroutine that takes parameters", jump.position)
|
||||
}
|
||||
}
|
||||
|
||||
val addr = jump.address
|
||||
if(addr!=null && (addr < 0 || addr > 65535))
|
||||
if(addr!=null && addr > 65535u)
|
||||
errors.err("jump address must be valid integer 0..\$ffff", jump.position)
|
||||
super.visit(jump)
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
val addr = block.address
|
||||
if(addr!=null && (addr<0 || addr>65535)) {
|
||||
if(addr!=null && addr>65535u) {
|
||||
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
|
||||
}
|
||||
|
||||
@ -225,7 +227,8 @@ internal class AstChecker(private val program: Program,
|
||||
count++
|
||||
}
|
||||
override fun visit(jump: Jump) {
|
||||
count++
|
||||
if(!jump.isGosub)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
@ -415,11 +418,10 @@ internal class AstChecker(private val program: Program,
|
||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
|
||||
if (stmt is Subroutine) {
|
||||
val idt = assignment.target.inferType(program)
|
||||
if(!idt.isKnown) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("assignment target invalid dt")
|
||||
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.getOr(DataType.BYTE))) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
errors.err("return type mismatch: ${stmt.returntypes.single()} expected $idt", assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -429,8 +431,15 @@ internal class AstChecker(private val program: Program,
|
||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||
if(targetDt.isIterable)
|
||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD))
|
||||
errors.err("type of value doesn't match target", assignment.value.position)
|
||||
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
|
||||
if(targetDt.isUnknown) {
|
||||
if(assignment.target.identifier?.targetStatement(program)!=null)
|
||||
errors.err("target datatype is unknown", assignment.target.position)
|
||||
// otherwise, another error about missing symbol is already reported.
|
||||
} else {
|
||||
errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(assignment.value is TypecastExpression) {
|
||||
@ -490,7 +499,7 @@ internal class AstChecker(private val program: Program,
|
||||
if (assignment.value !is FunctionCall)
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), assignTarget,
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
|
||||
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
|
||||
}
|
||||
}
|
||||
@ -509,7 +518,7 @@ internal class AstChecker(private val program: Program,
|
||||
fun err(msg: String) = errors.err(msg, decl.position)
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
|
||||
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true)
|
||||
err("recursive var declaration")
|
||||
|
||||
// CONST can only occur on simple types (byte, word, float)
|
||||
@ -827,7 +836,7 @@ internal class AstChecker(private val program: Program,
|
||||
when(expr.operator){
|
||||
"/", "%" -> {
|
||||
val constvalRight = expr.right.constValue(program)
|
||||
val divisor = constvalRight?.number?.toDouble()
|
||||
val divisor = constvalRight?.number
|
||||
if(divisor==0.0)
|
||||
errors.err("division by zero", expr.right.position)
|
||||
if(expr.operator=="%") {
|
||||
@ -967,36 +976,43 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
|
||||
if(targetStatement!=null)
|
||||
if(targetStatement!=null) {
|
||||
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
||||
if (!functionCallStatement.void) {
|
||||
// check for unused return values
|
||||
if (targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
|
||||
if(targetStatement.returntypes.size==1)
|
||||
errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
|
||||
else
|
||||
errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
|
||||
}
|
||||
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
|
||||
val rt = builtinFunctionReturnType(targetStatement.name, functionCallStatement.args, program)
|
||||
if(rt.isKnown)
|
||||
errors.warn("result value of a function call is discarded (use void?)", functionCallStatement.position)
|
||||
}
|
||||
checkUnusedReturnValues(functionCallStatement, targetStatement, program, errors)
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() == "sort") {
|
||||
// sort is not supported on float arrays
|
||||
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
|
||||
if(idref!=null && idref.inferType(program) istype DataType.ARRAY_F) {
|
||||
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
val funcName = functionCallStatement.target.nameInSource
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
if(funcName.size==1) {
|
||||
// check some builtin function calls
|
||||
if(funcName[0] == "sort") {
|
||||
// sort is not supported on float arrays
|
||||
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
|
||||
if(idref!=null && idref.inferType(program) istype DataType.ARRAY_F) {
|
||||
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
else if(funcName[0] in arrayOf("pop", "popw")) {
|
||||
// can only pop into a variable, that has to have the correct type
|
||||
val idref = functionCallStatement.args[0]
|
||||
if(idref !is IdentifierReference) {
|
||||
if(idref is TypecastExpression) {
|
||||
val passByRef = idref.expression.inferType(program).isPassByReference
|
||||
if(idref.type!=DataType.UWORD || !passByRef)
|
||||
errors.err("invalid argument to pop, must be a variable with the correct type: ${functionCallStatement.args.first()}", functionCallStatement.args.first().position)
|
||||
} else {
|
||||
errors.err("invalid argument to pop, must be a variable with the correct type: ${functionCallStatement.args.first()}", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(funcName[0] in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val error =
|
||||
@ -1054,7 +1070,12 @@ internal class AstChecker(private val program: Program,
|
||||
ident = fcall.args[0] as? IdentifierReference
|
||||
}
|
||||
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
|
||||
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].uppercase())
|
||||
var regname = ident.nameInSource[1].uppercase()
|
||||
if(regname.endsWith('L'))
|
||||
regname=regname.substring(0, regname.length-1)
|
||||
if(regname.endsWith('s'))
|
||||
regname=regname.substring(0, regname.length-1)
|
||||
val reg = RegisterOrPair.valueOf(regname)
|
||||
val same = params.filter { it.value.registerOrPair==reg }
|
||||
for(s in same) {
|
||||
if(s.index!=arg.index) {
|
||||
@ -1274,7 +1295,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
when (targetDt) {
|
||||
DataType.FLOAT -> {
|
||||
val number=value.number.toDouble()
|
||||
val number=value.number
|
||||
if (number > 1.7014118345e+38 || number < -1.7014118345e+38)
|
||||
return err("value '$number' out of range for MFLPT format")
|
||||
}
|
||||
@ -1350,7 +1371,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
private fun checkAssignmentCompatible(targetDatatype: DataType,
|
||||
target: AssignTarget,
|
||||
sourceDatatype: DataType,
|
||||
sourceValue: Expression,
|
||||
position: Position) : Boolean {
|
||||
@ -1393,3 +1413,19 @@ internal class AstChecker(private val program: Program,
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, program: Program, errors: IErrorReporter) {
|
||||
if (!call.void) {
|
||||
// check for unused return values
|
||||
if (target is Subroutine && target.returntypes.isNotEmpty()) {
|
||||
if (target.returntypes.size == 1)
|
||||
errors.warn("result value of subroutine call is discarded (use void?)", call.position)
|
||||
else
|
||||
errors.warn("result values of subroutine call are discarded (use void?)", call.position)
|
||||
} else if (target is BuiltinFunctionStatementPlaceholder) {
|
||||
val rt = builtinFunctionReturnType(target.name, call.args, program)
|
||||
if (rt.isKnown)
|
||||
errors.warn("result value of a function call is discarded (use void?)", call.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
@ -28,8 +29,8 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements(errors: IErrorReporter) {
|
||||
val reorder = StatementReorderer(this, errors)
|
||||
internal fun Program.reorderStatements(errors: IErrorReporter, options: CompilationOptions) {
|
||||
val reorder = StatementReorderer(this, errors, options)
|
||||
reorder.visit(this)
|
||||
if(errors.noErrors()) {
|
||||
reorder.applyModifications()
|
||||
@ -44,7 +45,7 @@ internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
|
||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
char,
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toInt(), char.position),
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toDouble(), char.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
@ -64,8 +65,8 @@ internal fun Program.verifyFunctionArgTypes() {
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.preprocessAst() {
|
||||
val transforms = AstPreprocessor()
|
||||
internal fun Program.preprocessAst(program: Program) {
|
||||
val transforms = AstPreprocessor(program)
|
||||
transforms.visit(this)
|
||||
var mods = transforms.applyModifications()
|
||||
while(mods>0)
|
||||
|
@ -75,7 +75,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, private
|
||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||
for(name in paramsToCheck) {
|
||||
val symbol = subroutine.searchSymbol(name)
|
||||
if(symbol!=null && symbol.position != subroutine.position)
|
||||
if(symbol!=null && (symbol as? VarDecl)?.subroutineParameter==null)
|
||||
nameError(name, symbol.position, subroutine)
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,13 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
@ -12,7 +16,40 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
|
||||
|
||||
class AstPreprocessor : AstWalker() {
|
||||
class AstPreprocessor(val program: Program) : AstWalker() {
|
||||
|
||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
// has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(range.from !is NumericLiteralValue) {
|
||||
try {
|
||||
val constval = range.from.constValue(program)
|
||||
if (constval != null)
|
||||
modifications += IAstModification.ReplaceNode(range.from, constval, range)
|
||||
} catch (x: SyntaxError) {
|
||||
// syntax errors will be reported later
|
||||
}
|
||||
}
|
||||
if(range.to !is NumericLiteralValue) {
|
||||
try {
|
||||
val constval = range.to.constValue(program)
|
||||
if(constval!=null)
|
||||
modifications += IAstModification.ReplaceNode(range.to, constval, range)
|
||||
} catch (x: SyntaxError) {
|
||||
// syntax errors will be reported later
|
||||
}
|
||||
}
|
||||
if(range.step !is NumericLiteralValue) {
|
||||
try {
|
||||
val constval = range.step.constValue(program)
|
||||
if(constval!=null)
|
||||
modifications += IAstModification.ReplaceNode(range.step, constval, range)
|
||||
} catch (x: SyntaxError) {
|
||||
// syntax errors will be reported later
|
||||
}
|
||||
}
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
|
@ -20,13 +20,13 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
val symbolsInSub = subroutine.allDefinedSymbols
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||
if(!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty()) {
|
||||
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||
return subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
.map {
|
||||
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||
val vardecl = VarDecl.fromParameter(it)
|
||||
IAstModification.InsertFirst(vardecl, subroutine)
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,14 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||
internal class StatementReorderer(val program: Program,
|
||||
val errors: IErrorReporter,
|
||||
private val options: CompilationOptions) : AstWalker() {
|
||||
// Reorders the statements in a way the compiler needs.
|
||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||
// - library blocks are put last.
|
||||
@ -21,12 +25,13 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||
// - sorts the choices in when statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
|
||||
// - replace subroutine calls (statement) by just assigning the arguments to the parameters and then a GoSub to the routine.
|
||||
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
|
||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: UInt.MAX_VALUE }).toMutableList()
|
||||
|
||||
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||
if(mainBlock!=null && mainBlock.address==null) {
|
||||
@ -53,15 +58,12 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
decl.value = null
|
||||
if(decl.name.startsWith("retval_interm_") && decl.definingScope.name=="prog8_lib") {
|
||||
// no need to zero out the special internal returnvalue intermediates.
|
||||
// TODO these variables are no longer used???
|
||||
return noModifications
|
||||
}
|
||||
val nextStmt = decl.nextSibling()
|
||||
val nextAssign = nextStmt as? Assignment
|
||||
val nextFor = nextStmt as? ForLoop
|
||||
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
|
||||
val hasNextAssignment = nextAssign!=null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY)
|
||||
if (!hasNextAssignment && !hasNextForWithThisLoopvar) {
|
||||
if (!hasNextForWithThisLoopvar) {
|
||||
// Add assignment to initialize with zero
|
||||
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
|
||||
val identifier = IdentifierReference(listOf(decl.name), decl.position)
|
||||
@ -125,6 +127,30 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
subs.map { IAstModification.InsertLast(it, subroutine) }
|
||||
}
|
||||
|
||||
if(!subroutine.isAsmSubroutine) {
|
||||
// change 'str' parameters into 'uword' (just treat it as an address)
|
||||
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
|
||||
val parameterChanges = stringParams.map {
|
||||
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
|
||||
IAstModification.ReplaceNode(it, uwordParam, subroutine)
|
||||
}
|
||||
|
||||
val stringParamsByNames = stringParams.associateBy { it.name }
|
||||
val varsChanges =
|
||||
if(stringParamsByNames.isNotEmpty()) {
|
||||
subroutine.statements
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames }
|
||||
.map {
|
||||
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, stringParamsByNames.getValue(it.name), it.position)
|
||||
IAstModification.ReplaceNode(it, newvar, subroutine)
|
||||
}
|
||||
}
|
||||
else emptyList()
|
||||
|
||||
return parameterChanges + varsChanges
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -170,8 +196,9 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
is Subroutine -> {
|
||||
val paramType = callee.parameters[argnum].type
|
||||
if(leftDt isAssignableTo paramType) {
|
||||
val cast = TypecastExpression(expr.left, paramType, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
val (replaced, cast) = expr.left.typecastTo(paramType, leftDt.getOr(DataType.UNDEFINED), true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
@ -179,8 +206,9 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
val paramTypes = func.parameters[argnum].possibleDatatypes
|
||||
for(type in paramTypes) {
|
||||
if(leftDt isAssignableTo type) {
|
||||
val cast = TypecastExpression(expr.left, type, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
val (replaced, cast) = expr.left.typecastTo(type, leftDt.getOr(DataType.UNDEFINED), true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -196,7 +224,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
// generating the wrong results later
|
||||
|
||||
fun wrapped(expr: Expression): Expression =
|
||||
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
|
||||
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0.0, expr.position), expr.position)
|
||||
|
||||
fun isLogicalExpr(expr: Expression?): Boolean {
|
||||
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
|
||||
@ -334,4 +362,110 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
val function = functionCallStatement.target.targetStatement(program)!!
|
||||
checkUnusedReturnValues(functionCallStatement, function, program, errors)
|
||||
if(function is Subroutine) {
|
||||
if(function.inline)
|
||||
return noModifications
|
||||
return if(function.isAsmSubroutine)
|
||||
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent)
|
||||
else
|
||||
replaceCallSubStatementWithGosub(function, functionCallStatement, parent)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
|
||||
}
|
||||
|
||||
val assignParams =
|
||||
function.parameters.zip(call.args).map {
|
||||
var argumentValue = it.second
|
||||
val paramIdentifier = IdentifierReference(function.scopedName + it.first.name, argumentValue.position)
|
||||
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(argDt in ArrayDatatypes) {
|
||||
// pass the address of the array instead
|
||||
argumentValue = AddressOf(argumentValue as IdentifierReference, argumentValue.position)
|
||||
}
|
||||
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position)
|
||||
}
|
||||
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
|
||||
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
} else if(!options.compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, function.asmParameterRegisters)) {
|
||||
// No register clobber risk, let the asmgen assign values to the registers directly.
|
||||
// this is more efficient than first evaluating them to the stack.
|
||||
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
|
||||
return noModifications
|
||||
} else {
|
||||
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
|
||||
if (options.slowCodegenWarnings)
|
||||
errors.warn("slow argument passing used to avoid register clobbering", call.position)
|
||||
val argOrder = options.compTarget.asmsubArgsEvalOrder(function)
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
argOrder.reversed().forEach {
|
||||
val arg = call.args[it]
|
||||
val param = function.parameters[it]
|
||||
scope.statements += pushCall(arg, param.type, arg.position)
|
||||
}
|
||||
// ... and pop them off again into the registers.
|
||||
argOrder.forEach {
|
||||
val param = function.parameters[it]
|
||||
val targetName = function.scopedName + param.name
|
||||
scope.statements += popCall(targetName, param.type, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
}
|
||||
|
||||
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
|
||||
mutableListOf(IdentifierReference(targetName, position)),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
|
||||
val pushvalue = when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> value
|
||||
in PassByReferenceDatatypes -> value
|
||||
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
|
||||
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
|
||||
else -> throw FatalAstException("invalid dt $dt $value")
|
||||
}
|
||||
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
|
||||
mutableListOf(pushvalue),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -46,8 +46,29 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
||||
|
||||
// convert a negative operand for bitwise operator to the 2's complement positive number instead
|
||||
if(expr.operator in bitwiseOperators && leftDt.isInteger && rightDt.isInteger) {
|
||||
val leftCv = expr.left.constValue(program)
|
||||
if(leftCv!=null && leftCv.number<0) {
|
||||
val value = if(rightDt.isBytes) 256+leftCv.number else 65536+leftCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.left,
|
||||
NumericLiteralValue(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position),
|
||||
expr))
|
||||
}
|
||||
val rightCv = expr.right.constValue(program)
|
||||
if(rightCv!=null && rightCv.number<0) {
|
||||
val value = if(leftDt.isBytes) 256+rightCv.number else 65536+rightCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.right,
|
||||
NumericLiteralValue(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
|
||||
expr))
|
||||
}
|
||||
}
|
||||
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.operator, expr.right)
|
||||
if(toFix!=null) {
|
||||
return when {
|
||||
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||
@ -171,6 +192,18 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
call as Node)
|
||||
break
|
||||
}
|
||||
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
|
||||
// We allow STR/ARRAY values in place of UWORD parameters.
|
||||
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
|
||||
val identifier = pair.second as? IdentifierReference
|
||||
if(identifier?.isSubroutineParameter(program)==false) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
AddressOf(identifier, pair.second.position),
|
||||
call as Node)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,14 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
if(sourceDt istype typecast.type)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
if(parent is Assignment) {
|
||||
val targetDt = (parent).target.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(sourceDt istype targetDt) {
|
||||
// we can get rid of this typecast because the type is already
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -86,6 +94,13 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $assignment")
|
||||
|
||||
val nextAssign = assignment.nextSibling() as? Assignment
|
||||
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) {
|
||||
if(nextAssign.value isSameAs assignment.value)
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
@ -83,8 +83,13 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
|
||||
if (!anyCompatible) {
|
||||
val actual = pair.first.toString()
|
||||
val expected = pair.second.toString()
|
||||
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
|
||||
return if(pair.second.size==1) {
|
||||
val expected = pair.second[0].toString()
|
||||
"argument ${index + 1} type mismatch, was: $actual expected: $expected"
|
||||
} else {
|
||||
val expected = pair.second.toList().toString()
|
||||
"argument ${index + 1} type mismatch, was: $actual expected one of: $expected"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,6 @@ package prog8tests
|
||||
|
||||
import com.github.michaelbull.result.getErrorOrElse
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.hamcrest.Matchers.nullValue
|
||||
import org.hamcrest.core.Is
|
||||
import org.junit.jupiter.api.*
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.compiler.ModuleImporter
|
||||
@ -19,119 +14,91 @@ import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import kotlin.io.path.*
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.fail
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeIn
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestModuleImporter {
|
||||
private val count = listOf("1st", "2nd", "3rd", "4th", "5th")
|
||||
class TestModuleImporter: FunSpec({
|
||||
val count = listOf("1st", "2nd", "3rd", "4th", "5th")
|
||||
|
||||
private lateinit var program: Program
|
||||
@BeforeEach
|
||||
fun beforeEach() {
|
||||
lateinit var program: Program
|
||||
|
||||
beforeTest {
|
||||
program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
}
|
||||
|
||||
private fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
|
||||
fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
|
||||
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(false), searchIn.toList())
|
||||
|
||||
fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
|
||||
return makeImporter(errors, searchIn.asList())
|
||||
}
|
||||
|
||||
private fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
|
||||
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(false), searchIn.toList())
|
||||
context("ImportModule") {
|
||||
|
||||
@Nested
|
||||
inner class Constructor {
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: invalid entries in search list")
|
||||
fun testInvalidEntriesInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: literal duplicates in search list")
|
||||
fun testLiteralDuplicatesInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: factual duplicates in search list")
|
||||
fun testFactualDuplicatesInSearchList() {}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ImportModule {
|
||||
|
||||
@Nested
|
||||
inner class WithInvalidPath {
|
||||
@Test
|
||||
fun testNonexisting() {
|
||||
context("WithInvalidPath") {
|
||||
test("testNonexisting") {
|
||||
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
|
||||
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${error1.file}", equalTo("${error1.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
error1.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
withClue(".file should be normalized") {
|
||||
"${error1.file}" shouldBe "${error1.file.normalize()}"
|
||||
}
|
||||
withClue(".file should point to specified path") {
|
||||
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
|
||||
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${error2.file}", equalTo("${error2.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
error2.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
withClue(".file should be normalized") {
|
||||
"${error2.file}" shouldBe "${error2.file.normalize()}"
|
||||
}
|
||||
withClue(".file should point to specified path") {
|
||||
error2.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDirectory() {
|
||||
test("testDirectory") {
|
||||
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
|
||||
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathRel) }
|
||||
shouldThrow<AccessDeniedException> { importer.importModule(srcPathRel) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
withClue(".file should be normalized") {
|
||||
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||
}
|
||||
withClue(".file should point to specified path") {
|
||||
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
|
||||
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathAbs) }
|
||||
shouldThrow<AccessDeniedException> { importer.importModule(srcPathAbs) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
withClue(".file should be normalized") {
|
||||
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||
}
|
||||
withClue(".file should point to specified path") {
|
||||
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithValidPath {
|
||||
context("WithValidPath") {
|
||||
|
||||
@Test
|
||||
fun testAbsolute() {
|
||||
test("testAbsolute") {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
@ -140,29 +107,29 @@ class TestModuleImporter {
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
|
||||
val module = importer.importModule(path.absolute()).getOrElse { throw it }
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
program.modules.size shouldBe 2
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeToWorkingDir() {
|
||||
test("testRelativeToWorkingDir") {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
assertThat("sanity check: path should NOT be absolute", path.isAbsolute, equalTo(false))
|
||||
withClue("sanity check: path should NOT be absolute") {
|
||||
path.isAbsolute shouldBe false
|
||||
}
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
program.modules.size shouldBe 2
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeTo1stDirInSearchList() {
|
||||
test("testRelativeTo1stDirInSearchList") {
|
||||
val searchIn = Path(".")
|
||||
.div(workingDir.relativize(fixturesDir))
|
||||
.invariantSeparatorsPathString
|
||||
@ -172,51 +139,32 @@ class TestModuleImporter {
|
||||
assumeReadableFile(searchIn, path)
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
program.modules.size shouldBe 2
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: relative to 2nd in search list")
|
||||
fun testRelativeTo2ndDirInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: ambiguous - 2 or more really different candidates")
|
||||
fun testAmbiguousCandidates() {}
|
||||
|
||||
@Nested
|
||||
inner class WithBadFile {
|
||||
@Test
|
||||
fun testWithSyntaxError() {
|
||||
context("WithBadFile") {
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(srcPath) }
|
||||
|
||||
repeat(2) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>() { act() }.let {
|
||||
it.position.file shouldBe SourceCode.relative(srcPath).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_once() {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_twice() {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
|
||||
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
@ -224,28 +172,33 @@ class TestModuleImporter {
|
||||
|
||||
val act = { importer.importModule(importing) }
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>() { act() }.let {
|
||||
it.position.file shouldBe SourceCode.relative(imported).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
assertThat("imported module with error in it should not be present", program.modules.size, equalTo(1))
|
||||
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
|
||||
}
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
}
|
||||
}
|
||||
|
||||
test("testImportingFileWithSyntaxError_once") {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
test("testImportingFileWithSyntaxError_twice") {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ImportLibraryModule {
|
||||
@Nested
|
||||
inner class WithInvalidName {
|
||||
@Test
|
||||
fun testWithNonExistingName() {
|
||||
context("ImportLibraryModule") {
|
||||
context("WithInvalidName") {
|
||||
test("testWithNonExistingName") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val errors = ErrorReporterForTests(false)
|
||||
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
|
||||
@ -254,46 +207,45 @@ class TestModuleImporter {
|
||||
|
||||
repeat(2) { n ->
|
||||
val result = importer.importLibraryModule(filenameNoExt)
|
||||
assertThat(count[n] + " call / NO .p8 extension", result, Is(nullValue()))
|
||||
assertFalse(errors.noErrors(), count[n] + " call / NO .p8 extension")
|
||||
assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist")
|
||||
withClue(count[n] + " call / NO .p8 extension") { result shouldBe null }
|
||||
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
|
||||
errors.report()
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
|
||||
val result2 = importer.importLibraryModule(filenameWithExt)
|
||||
assertThat(count[n] + " call / with .p8 extension", result2, Is(nullValue()))
|
||||
assertFalse(importer.errors.noErrors(), count[n] + " call / with .p8 extension")
|
||||
assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist.p8")
|
||||
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
|
||||
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
|
||||
errors.report()
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithValidName {
|
||||
@Nested
|
||||
inner class WithBadFile {
|
||||
@Test
|
||||
fun testWithSyntaxError() {
|
||||
context("WithValidName") {
|
||||
context("WithBadFile") {
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
repeat(2) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call")
|
||||
{ importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>()
|
||||
{
|
||||
importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||
it.position.file shouldBe SourceCode.relative(srcPath).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
@ -301,29 +253,29 @@ class TestModuleImporter {
|
||||
|
||||
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>() {
|
||||
act() }.let {
|
||||
it.position.file shouldBe SourceCode.relative(imported).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
}
|
||||
assertThat("imported module with error in it should not be present", program.modules.size, equalTo(1))
|
||||
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
importer.errors.report()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_once() {
|
||||
test("testImportingFileWithSyntaxError_once") {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_twice() {
|
||||
test("testImportingFileWithSyntaxError_twice") {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
58
compiler/test/ProjectConfig.kt
Normal file
58
compiler/test/ProjectConfig.kt
Normal file
@ -0,0 +1,58 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import io.kotest.core.listeners.Listener
|
||||
import io.kotest.core.listeners.TestListener
|
||||
import io.kotest.core.spec.Spec
|
||||
import io.kotest.extensions.system.NoSystemErrListener
|
||||
import io.kotest.extensions.system.NoSystemOutListener
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import kotlin.math.max
|
||||
|
||||
object ProjectConfig : AbstractProjectConfig() {
|
||||
override val parallelism = 2 // max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
// override fun listeners() = listOf(SystemOutToNullListener)
|
||||
}
|
||||
|
||||
//object SystemOutToNullListener: TestListener {
|
||||
// override suspend fun beforeSpec(spec: Spec) = setup()
|
||||
//
|
||||
// private fun setup() {
|
||||
// System.setOut(object: PrintStream(object: ByteArrayOutputStream(){
|
||||
// override fun write(p0: Int) {
|
||||
// // do nothing
|
||||
// }
|
||||
//
|
||||
// override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
// // do nothing
|
||||
// }
|
||||
//
|
||||
// override fun write(b: ByteArray) {
|
||||
// // do nothing
|
||||
// }
|
||||
// }){}
|
||||
// )
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//object SystemErrToNullListener: TestListener {
|
||||
// override suspend fun beforeSpec(spec: Spec) = setup()
|
||||
//
|
||||
// private fun setup() {
|
||||
// System.setErr(object: PrintStream(object: ByteArrayOutputStream(){
|
||||
// override fun write(p0: Int) {
|
||||
// // do nothing
|
||||
// }
|
||||
//
|
||||
// override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
// // do nothing
|
||||
// }
|
||||
//
|
||||
// override fun write(b: ByteArray) {
|
||||
// // do nothing
|
||||
// }
|
||||
// }){}
|
||||
// )
|
||||
// }
|
||||
//}
|
@ -1,21 +1,19 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.maps.shouldContainKey
|
||||
import io.kotest.matchers.maps.shouldNotContainKey
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compilerinterface.CallGraph
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCallgraph {
|
||||
@Test
|
||||
fun testGraphForEmptySubs() {
|
||||
class TestCallgraph: FunSpec({
|
||||
test("testGraphForEmptySubs") {
|
||||
val sourcecode = """
|
||||
%import string
|
||||
main {
|
||||
@ -28,32 +26,33 @@ class TestCallgraph {
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.program)
|
||||
|
||||
assertEquals(1, graph.imports.size)
|
||||
assertEquals(1, graph.importedBy.size)
|
||||
graph.imports.size shouldBe 1
|
||||
graph.importedBy.size shouldBe 1
|
||||
val toplevelModule = result.program.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
assertEquals("string", importedModule.name)
|
||||
importedModule.name shouldBe "string"
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
|
||||
importedBy.name.startsWith("on_the_fly_test") shouldBe true
|
||||
|
||||
assertFalse(graph.unused(toplevelModule))
|
||||
assertFalse(graph.unused(importedModule))
|
||||
graph.unused(toplevelModule) shouldBe false
|
||||
graph.unused(importedModule) shouldBe false
|
||||
|
||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||
for(stmt in mainBlock.statements) {
|
||||
val sub = stmt as Subroutine
|
||||
assertFalse(sub in graph.calls)
|
||||
assertFalse(sub in graph.calledBy)
|
||||
graph.calls shouldNotContainKey sub
|
||||
graph.calledBy shouldNotContainKey sub
|
||||
|
||||
if(sub === result.program.entrypoint)
|
||||
assertFalse(graph.unused(sub), "start() should always be marked as used to avoid having it removed")
|
||||
withClue("start() should always be marked as used to avoid having it removed") {
|
||||
graph.unused(sub) shouldBe false
|
||||
}
|
||||
else
|
||||
assertTrue(graph.unused(sub))
|
||||
graph.unused(sub) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGraphForEmptyButReferencedSub() {
|
||||
test("testGraphForEmptyButReferencedSub") {
|
||||
val sourcecode = """
|
||||
%import string
|
||||
main {
|
||||
@ -68,24 +67,32 @@ class TestCallgraph {
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.program)
|
||||
|
||||
assertEquals(1, graph.imports.size)
|
||||
assertEquals(1, graph.importedBy.size)
|
||||
graph.imports.size shouldBe 1
|
||||
graph.importedBy.size shouldBe 1
|
||||
val toplevelModule = result.program.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
assertEquals("string", importedModule.name)
|
||||
importedModule.name shouldBe "string"
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
|
||||
importedBy.name.startsWith("on_the_fly_test") shouldBe true
|
||||
|
||||
assertFalse(graph.unused(toplevelModule))
|
||||
assertFalse(graph.unused(importedModule))
|
||||
graph.unused(toplevelModule) shouldBe false
|
||||
graph.unused(importedModule) shouldBe false
|
||||
|
||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"}
|
||||
val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"}
|
||||
|
||||
assertTrue(startSub in graph.calls, "start 'calls' (references) empty")
|
||||
assertFalse(emptySub in graph.calls, "empty doesn't call anything")
|
||||
assertTrue(emptySub in graph.calledBy, "empty gets 'called'")
|
||||
assertFalse(startSub in graph.calledBy, "start doesn't get called (except as entrypoint ofc.)")
|
||||
withClue("start 'calls' (references) empty") {
|
||||
graph.calls shouldContainKey startSub
|
||||
}
|
||||
withClue("empty doesn't call anything") {
|
||||
graph.calls shouldNotContainKey emptySub
|
||||
}
|
||||
withClue("empty gets 'called'") {
|
||||
graph.calledBy shouldContainKey emptySub
|
||||
}
|
||||
withClue( "start doesn't get called (except as entrypoint ofc.)") {
|
||||
graph.calledBy shouldNotContainKey startSub
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,10 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
@ -11,9 +14,6 @@ import prog8.ast.statements.Assignment
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.assertNull
|
||||
|
||||
|
||||
/**
|
||||
@ -21,11 +21,9 @@ import kotlin.test.assertNull
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnCharLit {
|
||||
class TestCompilerOnCharLit: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testCharLitAsRomsubArg() {
|
||||
test("testCharLitAsRomsubArg") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
@ -40,15 +38,15 @@ class TestCompilerOnCharLit {
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
assertIs<NumericLiteralValue>(funCall.args[0],
|
||||
"char literal should have been replaced by ubyte literal")
|
||||
withClue("char literal should have been replaced by ubyte literal") {
|
||||
funCall.args[0] shouldBe instanceOf<NumericLiteralValue>()
|
||||
}
|
||||
val arg = funCall.args[0] as NumericLiteralValue
|
||||
assertEquals(DataType.UBYTE, arg.type)
|
||||
assertEquals(platform.encodeString("\n", false)[0], arg.number.toShort())
|
||||
arg.type shouldBe DataType.UBYTE
|
||||
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharVarAsRomsubArg() {
|
||||
test("testCharVarAsRomsubArg") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
@ -64,28 +62,31 @@ class TestCompilerOnCharLit {
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
assertIs<IdentifierReference>(funCall.args[0])
|
||||
funCall.args[0] shouldBe instanceOf<IdentifierReference>()
|
||||
val arg = funCall.args[0] as IdentifierReference
|
||||
val decl = arg.targetVarDecl(program)!!
|
||||
assertEquals(VarDeclType.VAR, decl.type)
|
||||
assertEquals(DataType.UBYTE, decl.datatype)
|
||||
decl.type shouldBe VarDeclType.VAR
|
||||
decl.datatype shouldBe DataType.UBYTE
|
||||
|
||||
// TODO: assertIs<CharLiteral>(decl.value,
|
||||
// "char literals should be kept until code gen")
|
||||
// val initializerValue = decl.value as CharLiteral
|
||||
// assertEquals('\n', (initializerValue as CharLiteral).value)
|
||||
|
||||
assertNull(decl.value, "initializer value should have been moved to separate assignment")
|
||||
withClue("initializer value should have been moved to separate assignment"){
|
||||
decl.value shouldBe null
|
||||
}
|
||||
val assignInitialValue = decl.nextSibling() as Assignment
|
||||
assertEquals(listOf("ch"), assignInitialValue.target.identifier!!.nameInSource)
|
||||
assertIs<NumericLiteralValue>(assignInitialValue.value, "char literal should have been replaced by ubyte literal")
|
||||
assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch")
|
||||
withClue("char literal should have been replaced by ubyte literal") {
|
||||
assignInitialValue.value shouldBe instanceOf<NumericLiteralValue>()
|
||||
}
|
||||
val initializerValue = assignInitialValue.value as NumericLiteralValue
|
||||
assertEquals(DataType.UBYTE, initializerValue.type)
|
||||
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort())
|
||||
initializerValue.type shouldBe DataType.UBYTE
|
||||
initializerValue.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharConstAsRomsubArg() {
|
||||
test("testCharConstAsRomsubArg") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
@ -105,20 +106,15 @@ class TestCompilerOnCharLit {
|
||||
when (val arg = funCall.args[0]) {
|
||||
is IdentifierReference -> {
|
||||
val decl = arg.targetVarDecl(program)!!
|
||||
assertEquals(VarDeclType.CONST, decl.type)
|
||||
assertEquals(DataType.UBYTE, decl.datatype)
|
||||
assertEquals(
|
||||
platform.encodeString("\n", false)[0],
|
||||
(decl.value as NumericLiteralValue).number.toShort())
|
||||
decl.type shouldBe VarDeclType.CONST
|
||||
decl.datatype shouldBe DataType.UBYTE
|
||||
(decl.value as NumericLiteralValue).number shouldBe platform.encodeString("\n", false)[0]
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
assertEquals(
|
||||
platform.encodeString("\n", false)[0],
|
||||
arg.number.toShort())
|
||||
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
}
|
||||
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
|
||||
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
@ -1,18 +1,15 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8tests.ast.helpers.assumeDirectory
|
||||
import prog8tests.ast.helpers.mapCombinations
|
||||
import prog8tests.ast.helpers.outputDir
|
||||
import prog8tests.ast.helpers.workingDir
|
||||
import prog8tests.ast.helpers.*
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.exists
|
||||
|
||||
@ -22,41 +19,99 @@ import kotlin.io.path.exists
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
// @Disabled("disable to save some time")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnExamples {
|
||||
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||
|
||||
private fun makeDynamicCompilerTest(name: String, platform: ICompilationTarget, optimize: Boolean) : DynamicTest {
|
||||
val searchIn = mutableListOf(examplesDir)
|
||||
if (platform == Cx16Target) {
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn
|
||||
.map { it.resolve("$name.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { workingDir.relativize(it) }
|
||||
.first { it.exists() }
|
||||
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${platform.name}, optimize=$optimize"
|
||||
return dynamicTest(displayName) {
|
||||
compileProgram(
|
||||
filepath,
|
||||
optimize,
|
||||
optimizeFloatExpressions = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
compilationTarget = platform.name,
|
||||
sourceDirs = listOf(),
|
||||
outputDir
|
||||
).assertSuccess("; $displayName")
|
||||
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||
|
||||
private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilationTarget): CompilationResult {
|
||||
val args = CompilerArguments(
|
||||
filepath,
|
||||
optimize,
|
||||
optimizeFloatExpressions = true,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
compilationTarget = target.name,
|
||||
outputDir = outputDir
|
||||
)
|
||||
return compileProgram(args)
|
||||
}
|
||||
|
||||
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
|
||||
val searchIn = mutableListOf(examplesDir)
|
||||
if (target == Cx16Target) {
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn
|
||||
.map { it.resolve("$source.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { workingDir.relativize(it) }
|
||||
.first { it.exists() }
|
||||
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${target.name}, optimize=$optimize"
|
||||
return Pair(displayName, filepath)
|
||||
}
|
||||
|
||||
|
||||
class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
val onlyC64 = cartesianProduct(
|
||||
listOf(
|
||||
"balloonflight",
|
||||
"bdmusic",
|
||||
"bdmusic-irq",
|
||||
"charset",
|
||||
"cube3d-sprites",
|
||||
"plasma",
|
||||
"sprites",
|
||||
"turtle-gfx",
|
||||
"wizzine",
|
||||
),
|
||||
listOf(false, true)
|
||||
)
|
||||
|
||||
onlyC64.forEach {
|
||||
val (source, optimize) = it
|
||||
val (displayName, filepath) = prepareTestFiles(source, optimize, C64Target)
|
||||
test(displayName) {
|
||||
compileTheThing(filepath, optimize, C64Target).assertSuccess()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@TestFactory
|
||||
// @Disabled("disable to save some time")
|
||||
fun bothCx16AndC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
val onlyCx16 = cartesianProduct(
|
||||
listOf(
|
||||
"vtui/testvtui",
|
||||
"amiga",
|
||||
"bobs",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
"datetime",
|
||||
"highresbitmap",
|
||||
"kefrenbars",
|
||||
"mandelbrot-gfx-colors",
|
||||
"multipalette",
|
||||
"rasterbars",
|
||||
"sincos",
|
||||
"tehtriz",
|
||||
"testgfx2",
|
||||
),
|
||||
listOf(false, true)
|
||||
)
|
||||
|
||||
onlyCx16.forEach {
|
||||
val (source, optimize) = it
|
||||
val (displayName, filepath) = prepareTestFiles(source, optimize, Cx16Target)
|
||||
test(displayName) {
|
||||
compileTheThing(filepath, optimize, Cx16Target).assertSuccess()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
|
||||
val bothCx16AndC64 = cartesianProduct(
|
||||
listOf(
|
||||
"animals",
|
||||
"balls",
|
||||
"cube3d",
|
||||
@ -79,48 +134,18 @@ class TestCompilerOnExamples {
|
||||
"tehtriz",
|
||||
"textelite",
|
||||
),
|
||||
dim2 = listOf(Cx16Target, C64Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
listOf(false, true)
|
||||
)
|
||||
|
||||
@TestFactory
|
||||
// @Disabled("disable to save some time")
|
||||
fun onlyC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"balloonflight",
|
||||
"bdmusic",
|
||||
"bdmusic-irq",
|
||||
"charset",
|
||||
"cube3d-sprites",
|
||||
"plasma",
|
||||
"sprites",
|
||||
"turtle-gfx",
|
||||
"wizzine",
|
||||
),
|
||||
dim2 = listOf(C64Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
|
||||
@TestFactory
|
||||
// @Disabled("disable to save some time")
|
||||
fun onlyCx16() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"vtui/testvtui",
|
||||
"amiga",
|
||||
"bobs",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
"datetime",
|
||||
"highresbitmap",
|
||||
"kefrenbars",
|
||||
"mandelbrot-gfx-colors",
|
||||
"multipalette",
|
||||
"testgfx2",
|
||||
),
|
||||
dim2 = listOf(Cx16Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
}
|
||||
bothCx16AndC64.forEach {
|
||||
val (source, optimize) = it
|
||||
val (displayNameC64, filepathC64) = prepareTestFiles(source, optimize, C64Target)
|
||||
val (displayNameCx16, filepathCx16) = prepareTestFiles(source, optimize, Cx16Target)
|
||||
test(displayNameC64) {
|
||||
compileTheThing(filepathC64, optimize, C64Target).assertSuccess()
|
||||
}
|
||||
test(displayNameCx16) {
|
||||
compileTheThing(filepathCx16, optimize, Cx16Target).assertSuccess()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,9 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
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.AddressOf
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
@ -13,8 +15,6 @@ import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileFile
|
||||
import kotlin.io.path.name
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
|
||||
/**
|
||||
@ -22,14 +22,11 @@ import kotlin.test.assertNotEquals
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnImportsAndIncludes {
|
||||
class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
|
||||
@Nested
|
||||
inner class Import {
|
||||
context("Import") {
|
||||
|
||||
@Test
|
||||
fun testImportFromSameFolder() {
|
||||
test("testImportFromSameFolder") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.p8")
|
||||
|
||||
@ -44,17 +41,15 @@ class TestCompilerOnImportsAndIncludes {
|
||||
.map { it.args[0] as IdentifierReference }
|
||||
.map { it.targetVarDecl(program)!!.value as StringLiteralValue }
|
||||
|
||||
assertEquals("main.bar", strLits[0].value)
|
||||
assertEquals("foo.bar", strLits[1].value)
|
||||
assertEquals("main", strLits[0].definingScope.name)
|
||||
assertEquals("foo", strLits[1].definingScope.name)
|
||||
strLits[0].value shouldBe "main.bar"
|
||||
strLits[1].value shouldBe "foo.bar"
|
||||
strLits[0].definingScope.name shouldBe "main"
|
||||
strLits[1].definingScope.name shouldBe "foo"
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class AsmInclude {
|
||||
@Test
|
||||
fun testAsmIncludeFromSameFolder() {
|
||||
context("AsmInclude") {
|
||||
test("testAsmIncludeFromSameFolder") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.asm")
|
||||
|
||||
@ -69,20 +64,18 @@ class TestCompilerOnImportsAndIncludes {
|
||||
.map { it.args[0] }
|
||||
|
||||
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteralValue
|
||||
assertEquals("main.bar", str0.value)
|
||||
assertEquals("main", str0.definingScope.name)
|
||||
str0.value shouldBe "main.bar"
|
||||
str0.definingScope.name shouldBe "main"
|
||||
|
||||
val id1 = (args[1] as AddressOf).identifier
|
||||
val lbl1 = id1.targetStatement(program) as Label
|
||||
assertEquals("foo_bar", lbl1.name)
|
||||
assertEquals("start", lbl1.definingScope.name)
|
||||
lbl1.name shouldBe "foo_bar"
|
||||
lbl1.definingScope.name shouldBe "start"
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Asmbinary {
|
||||
@Test
|
||||
fun testAsmbinaryDirectiveWithNonExistingFile() {
|
||||
context("Asmbinary") {
|
||||
test("testAsmbinaryDirectiveWithNonExistingFile") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
|
||||
|
||||
@ -90,8 +83,7 @@ class TestCompilerOnImportsAndIncludes {
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAsmbinaryDirectiveWithNonReadableFile() {
|
||||
test("testAsmbinaryDirectiveWithNonReadableFile") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
|
||||
assumeDirectory(fixturesDir, "subFolder")
|
||||
|
||||
@ -99,31 +91,30 @@ class TestCompilerOnImportsAndIncludes {
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
fun asmbinaryDirectiveWithExistingBinFile(): Iterable<DynamicTest> =
|
||||
listOf(
|
||||
val tests = listOf(
|
||||
Triple("same ", "asmBinaryFromSameFolder.p8", "do_nothing1.bin"),
|
||||
Triple("sub", "asmBinaryFromSubFolder.p8", "subFolder/do_nothing2.bin"),
|
||||
).map {
|
||||
val (where, p8Str, binStr) = it
|
||||
dynamicTest("%asmbinary from ${where}folder") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
// val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
assertNotEquals( // the bug we're testing for (#54) was hidden if outputDir == workingDir
|
||||
workingDir.normalize().toAbsolutePath(),
|
||||
outputDir.normalize().toAbsolutePath(),
|
||||
"sanity check: workingDir and outputDir should not be the same folder"
|
||||
)
|
||||
)
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertSuccess(
|
||||
"argument to assembler directive .binary " +
|
||||
"should be relative to the generated .asm file (in output dir), " +
|
||||
"NOT relative to .p8 neither current working dir"
|
||||
)
|
||||
tests.forEach {
|
||||
val (where, p8Str, binStr) = it
|
||||
test("%asmbinary from ${where}folder") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
// val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
|
||||
// the bug we're testing for (#54) was hidden if outputDir == workingDir
|
||||
withClue("sanity check: workingDir and outputDir should not be the same folder") {
|
||||
outputDir.normalize().toAbsolutePath() shouldNotBe workingDir.normalize().toAbsolutePath()
|
||||
}
|
||||
}
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertSuccess(
|
||||
"argument to assembler directive .binary " +
|
||||
"should be relative to the generated .asm file (in output dir), " +
|
||||
"NOT relative to .p8 neither current working dir"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
@ -1,26 +1,25 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compilerinterface.size
|
||||
import prog8.compilerinterface.toConstantIntegerRange
|
||||
import prog8tests.ast.helpers.mapCombinations
|
||||
import prog8tests.ast.helpers.cartesianProduct
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
/**
|
||||
@ -28,13 +27,11 @@ import kotlin.test.assertEquals
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnRanges {
|
||||
class TestCompilerOnRanges: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testUByteArrayInitializerWithRange_char_to_char() {
|
||||
test("testUByteArrayInitializerWithRange_char_to_char") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, true, """
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] cs = @'a' to 'z' ; values are computed at compile time
|
||||
@ -55,12 +52,15 @@ class TestCompilerOnRanges {
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.last() - rhsValues.first() + 1, "rangeExpr.size()")
|
||||
withClue(".first .. .last") {
|
||||
actualStr shouldBe expectedStr
|
||||
}
|
||||
withClue("rangeExpr.size()") {
|
||||
(rhsValues.last() - rhsValues.first() + 1) shouldBe (expectedEnd - expectedStart + 1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatArrayInitializerWithRange_char_to_char() {
|
||||
test("testFloatArrayInitializerWithRange_char_to_char") {
|
||||
val platform = C64Target
|
||||
val result = compileText(platform, optimize = false, """
|
||||
%option enable_floats
|
||||
@ -84,50 +84,39 @@ class TestCompilerOnRanges {
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.size, "rangeExpr.size()")
|
||||
withClue(".first .. .last") {
|
||||
actualStr shouldBe expectedStr
|
||||
}
|
||||
withClue("rangeExpr.size()") {
|
||||
rhsValues.size shouldBe (expectedEnd - expectedStart + 1)
|
||||
}
|
||||
}
|
||||
|
||||
fun Subroutine.decl(varName: String): VarDecl {
|
||||
return statements.filterIsInstance<VarDecl>()
|
||||
.first { it.name == varName }
|
||||
}
|
||||
inline fun <reified T : Expression> VarDecl.rhs() : T {
|
||||
return value as T
|
||||
}
|
||||
inline fun <reified T : Expression> ArrayLiteralValue.elements() : List<T> {
|
||||
return value.map { it as T }
|
||||
}
|
||||
context("floatArrayInitializerWithRange") {
|
||||
val combos = cartesianProduct(
|
||||
listOf("", "42", "41"), // sizeInDecl
|
||||
listOf("%option enable_floats", ""), // optEnableFloats
|
||||
listOf(Cx16Target, C64Target), // platform
|
||||
listOf(false, true) // optimize
|
||||
)
|
||||
|
||||
fun <N : Number> assertEndpoints(expFirst: N, expLast: N, actual: Iterable<N>, msg: String = ".first .. .last") {
|
||||
val expectedStr = "$expFirst .. $expLast"
|
||||
val actualStr = "${actual.first()} .. ${actual.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
}
|
||||
|
||||
|
||||
@TestFactory
|
||||
fun floatArrayInitializerWithRange() = mapCombinations(
|
||||
dim1 = listOf("", "42", "41"), // sizeInDecl
|
||||
dim2 = listOf("%option enable_floats", ""), // optEnableFloats
|
||||
dim3 = listOf(Cx16Target, C64Target), // platform
|
||||
dim4 = listOf(false, true), // optimize
|
||||
combine4 = { sizeInDecl, optEnableFloats, platform, optimize ->
|
||||
combos.forEach {
|
||||
val (sizeInDecl, optEnableFloats, platform, optimize) = it
|
||||
val displayName =
|
||||
"test failed for: " +
|
||||
when (sizeInDecl) {
|
||||
"" -> "no"
|
||||
"42" -> "correct"
|
||||
else -> "wrong"
|
||||
} + " array size given" +
|
||||
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
|
||||
", ${platform.name}, optimize: $optimize"
|
||||
dynamicTest(displayName) {
|
||||
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
|
||||
", ${platform.name}, optimize: $optimize"
|
||||
|
||||
test(displayName) {
|
||||
val result = compileText(platform, optimize, """
|
||||
$optEnableFloats
|
||||
main {
|
||||
sub start() {
|
||||
float[$sizeInDecl] cs = 1 to 42 ; values are computed at compile time
|
||||
float[$sizeInDecl] cs = 1 to 42 ; values are computed at compile time
|
||||
cs[0] = 23 ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
@ -136,12 +125,12 @@ class TestCompilerOnRanges {
|
||||
result.assertSuccess()
|
||||
else
|
||||
result.assertFailure()
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_char_to_char() {
|
||||
test("testForLoopWithRange_char_to_char") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
@ -167,12 +156,15 @@ class TestCompilerOnRanges {
|
||||
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(), "rangeExpr.size()")
|
||||
withClue(".first .. .last") {
|
||||
actualStr shouldBe expectedStr
|
||||
}
|
||||
withClue("rangeExpr.size()") {
|
||||
rangeExpr.size() shouldBe (expectedEnd - expectedStart + 1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_bool_to_bool() {
|
||||
test("testForLoopWithRange_bool_to_bool") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
@ -192,14 +184,13 @@ class TestCompilerOnRanges {
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
|
||||
assertEquals(2, rangeExpr.size())
|
||||
rangeExpr.size() shouldBe 2
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
assertEquals(0, intProgression?.first)
|
||||
assertEquals(1, intProgression?.last)
|
||||
intProgression?.first shouldBe 0
|
||||
intProgression?.last shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_ubyte_to_ubyte() {
|
||||
test("testForLoopWithRange_ubyte_to_ubyte") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
@ -219,14 +210,13 @@ class TestCompilerOnRanges {
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
|
||||
assertEquals(9, rangeExpr.size())
|
||||
rangeExpr.size() shouldBe 9
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
assertEquals(1, intProgression?.first)
|
||||
assertEquals(9, intProgression?.last)
|
||||
intProgression?.first shouldBe 1
|
||||
intProgression?.last shouldBe 9
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_str_downto_str() {
|
||||
test("testForLoopWithRange_str_downto_str") {
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target, true, """
|
||||
main {
|
||||
@ -238,13 +228,12 @@ class TestCompilerOnRanges {
|
||||
}
|
||||
}
|
||||
""", errors, false).assertFailure()
|
||||
assertEquals(2, errors.errors.size)
|
||||
assertContains(errors.errors[0], ".p8:5:29: range expression from value must be integer")
|
||||
assertContains(errors.errors[1], ".p8:5:44: range expression to value must be integer")
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain ".p8:5:29: range expression from value must be integer"
|
||||
errors.errors[1] shouldContain ".p8:5:44: range expression to value must be integer"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithIterable_str() {
|
||||
test("testForLoopWithIterable_str") {
|
||||
val result = compileText(Cx16Target, false, """
|
||||
main {
|
||||
sub start() {
|
||||
@ -263,18 +252,54 @@ class TestCompilerOnRanges {
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<IdentifierReference>()[0]
|
||||
|
||||
assertEquals(DataType.STR, iterable.inferType(program).getOr(DataType.UNDEFINED))
|
||||
iterable.inferType(program).getOr(DataType.UNDEFINED) shouldBe DataType.STR
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRangeExprNumericSize() {
|
||||
test("testRangeExprNumericSize") {
|
||||
val expr = RangeExpr(
|
||||
NumericLiteralValue.optimalInteger(10, Position.DUMMY),
|
||||
NumericLiteralValue.optimalInteger(20, Position.DUMMY),
|
||||
NumericLiteralValue.optimalInteger(2, Position.DUMMY),
|
||||
Position.DUMMY)
|
||||
assertEquals(6, expr.size())
|
||||
expr.size() shouldBe 6
|
||||
expr.toConstantIntegerRange()
|
||||
}
|
||||
}
|
||||
|
||||
test("range with negative step should be constvalue") {
|
||||
val result = compileText(C64Target, false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] array = 100 to 50 step -2
|
||||
ubyte xx
|
||||
for xx in 100 to 50 step -2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
val statements = result.program.entrypoint.statements
|
||||
val array = (statements[0] as VarDecl).value
|
||||
array shouldBe instanceOf<ArrayLiteralValue>()
|
||||
(array as ArrayLiteralValue).value.size shouldBe 26
|
||||
val forloop = (statements.dropLast(1).last() as ForLoop)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpr>()
|
||||
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("range with start/end variables should be ok") {
|
||||
val result = compileText(C64Target, false, """
|
||||
main {
|
||||
sub start() {
|
||||
byte from = 100
|
||||
byte end = 50
|
||||
byte xx
|
||||
for xx in from to end step -2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
val statements = result.program.entrypoint.statements
|
||||
val forloop = (statements.dropLast(1).last() as ForLoop)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpr>()
|
||||
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
}
|
||||
})
|
||||
|
@ -1,9 +1,8 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.ast.helpers.assumeReadableFile
|
||||
@ -22,13 +21,11 @@ import kotlin.io.path.writeText
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOptionSourcedirs {
|
||||
class TestCompilerOptionSourcedirs: FunSpec({
|
||||
|
||||
private lateinit var tempFileInWorkingDir: Path
|
||||
lateinit var tempFileInWorkingDir: Path
|
||||
|
||||
@BeforeAll
|
||||
fun setUp() {
|
||||
beforeSpec {
|
||||
tempFileInWorkingDir = createTempFile(directory = workingDir, prefix = "tmp_", suffix = ".p8")
|
||||
.also { it.writeText("""
|
||||
main {
|
||||
@ -38,13 +35,12 @@ class TestCompilerOptionSourcedirs {
|
||||
""")}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDown() {
|
||||
afterSpec {
|
||||
tempFileInWorkingDir.deleteExisting()
|
||||
}
|
||||
|
||||
private fun compileFile(filePath: Path, sourceDirs: List<String>) =
|
||||
compileProgram(
|
||||
fun compileFile(filePath: Path, sourceDirs: List<String>): CompilationResult {
|
||||
val args = CompilerArguments(
|
||||
filepath = filePath,
|
||||
optimize = false,
|
||||
optimizeFloatExpressions = false,
|
||||
@ -55,48 +51,44 @@ class TestCompilerOptionSourcedirs {
|
||||
sourceDirs,
|
||||
outputDir
|
||||
)
|
||||
return compileProgram(args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAbsoluteFilePathInWorkingDir() {
|
||||
test("testAbsoluteFilePathInWorkingDir") {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathInWorkingDirRelativeToWorkingDir() {
|
||||
test("testFilePathInWorkingDirRelativeToWorkingDir") {
|
||||
val filepath = assumeReadableFile(workingDir.relativize(tempFileInWorkingDir.absolute()))
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathInWorkingDirRelativeTo1stInSourcedirs() {
|
||||
test("testFilePathInWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir)
|
||||
compileFile(filepath.fileName, listOf(workingDir.toString()))
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAbsoluteFilePathOutsideWorkingDir() {
|
||||
test("testAbsoluteFilePathOutsideWorkingDir") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
compileFile(filepath.absolute(), listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathOutsideWorkingDirRelativeToWorkingDir() {
|
||||
test("testFilePathOutsideWorkingDirRelativeToWorkingDir") {
|
||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "simple_main.p8").absolute())
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs() {
|
||||
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val sourcedirs = listOf("$fixturesDir")
|
||||
compileFile(filepath.fileName, sourcedirs)
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,9 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldStartWith
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.compiler.determineCompilationOptions
|
||||
import prog8.compiler.parseImports
|
||||
@ -11,15 +13,11 @@ import prog8tests.ast.helpers.outputDir
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestImportedModulesOrderAndOptions {
|
||||
class TestImportedModulesOrderAndOptions: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testImportedModuleOrderAndMainModuleCorrect() {
|
||||
test("testImportedModuleOrderAndMainModuleCorrect") {
|
||||
val result = compileText(C64Target, false, """
|
||||
%import textio
|
||||
%import floats
|
||||
@ -30,25 +28,27 @@ main {
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
|
||||
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||
|
||||
val moduleNames = result.program.modules.map { it.name }
|
||||
assertTrue(moduleNames[0].startsWith("on_the_fly_test"), "main module must be first")
|
||||
assertEquals(listOf(
|
||||
"prog8_interned_strings",
|
||||
"textio",
|
||||
"syslib",
|
||||
"conv",
|
||||
"floats",
|
||||
"math",
|
||||
"prog8_lib"
|
||||
), moduleNames.drop(1), "module order in parse tree")
|
||||
|
||||
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
|
||||
withClue("main module must be first") {
|
||||
moduleNames[0] shouldStartWith "on_the_fly_test"
|
||||
}
|
||||
withClue("module order in parse tree") {
|
||||
moduleNames.drop(1) shouldBe listOf(
|
||||
"prog8_interned_strings",
|
||||
"textio",
|
||||
"syslib",
|
||||
"conv",
|
||||
"floats",
|
||||
"math",
|
||||
"prog8_lib"
|
||||
)
|
||||
}
|
||||
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompilationOptionsCorrectFromMain() {
|
||||
test("testCompilationOptionsCorrectFromMain") {
|
||||
val result = compileText(C64Target, false, """
|
||||
%import textio
|
||||
%import floats
|
||||
@ -61,15 +61,14 @@ main {
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
|
||||
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||
val options = determineCompilationOptions(result.program, C64Target)
|
||||
assertTrue(options.floats)
|
||||
assertEquals(ZeropageType.DONTUSE, options.zeropage)
|
||||
assertTrue(options.noSysInit)
|
||||
options.floats shouldBe true
|
||||
options.zeropage shouldBe ZeropageType.DONTUSE
|
||||
options.noSysInit shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() {
|
||||
test("testModuleOrderAndCompilationOptionsCorrectWithJustImports") {
|
||||
val errors = ErrorReporterForTests()
|
||||
val sourceText = """
|
||||
%import textio
|
||||
@ -88,17 +87,23 @@ main {
|
||||
filepath.toFile().writeText(sourceText)
|
||||
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
|
||||
|
||||
assertEquals(filenameBase, program.toplevelModule.name)
|
||||
assertEquals(1, importedfiles.size, "all imports other than the test source must have been internal resources library files")
|
||||
assertEquals(listOf(
|
||||
internedStringsModuleName,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
|
||||
), program.modules.map {it.name}, "module order in parse tree")
|
||||
assertTrue(options.floats)
|
||||
assertEquals(ZeropageType.DONTUSE, options.zeropage, "zeropage option must be correctly taken from main module, not from float module import logic")
|
||||
assertTrue(options.noSysInit)
|
||||
program.toplevelModule.name shouldBe filenameBase
|
||||
withClue("all imports other than the test source must have been internal resources library files") {
|
||||
importedfiles.size shouldBe 1
|
||||
}
|
||||
withClue("module order in parse tree") {
|
||||
program.modules.map { it.name } shouldBe
|
||||
listOf(
|
||||
internedStringsModuleName,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
|
||||
)
|
||||
}
|
||||
options.floats shouldBe true
|
||||
options.noSysInit shouldBe true
|
||||
withClue("zeropage option must be correctly taken from main module, not from float module import logic") {
|
||||
options.zeropage shouldBe ZeropageType.DONTUSE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
@ -12,179 +12,212 @@ import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compilerinterface.isInRegularRAMof
|
||||
import prog8.compilerinterface.isIOAddress
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestMemory {
|
||||
class TestMemory: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_addresses() {
|
||||
fun wrapWithProgram(statements: List<Statement>): Program {
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), statements.toMutableList(), false, Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
program.addModule(module)
|
||||
return program
|
||||
}
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
test("assignment target not in mapped IO space C64") {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0002, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memory_addresses() {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xeeee, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_identifiers() {
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
|
||||
test("assign target in mapped IO space C64") {
|
||||
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x0001, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xdfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
private fun createTestProgramForMemoryRefViaVar(program: Program, address: Int, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
module.linkIntoProgram(program)
|
||||
wrapWithProgram(listOf(decl, assignment))
|
||||
return target
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_expression() {
|
||||
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
test("identifier mapped to IO memory on C64") {
|
||||
var target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.VAR)
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.VAR)
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.CONST)
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.CONST)
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.MEMORY)
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.MEMORY)
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_variable() {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
|
||||
test("memory expression mapped to IO memory on C64") {
|
||||
var memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0xd020, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
printProgram(target.definingModule.program)
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
test("regular variable not in mapped IO ram on C64") {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memmap_variable() {
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
test("memory mapped variable not in mapped IO ram on C64") {
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memmap_variable() {
|
||||
val address = 0xd020
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
test("memory mapped variable in mapped IO ram on C64") {
|
||||
val address = 0xd020u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array() {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
|
||||
test("array not in mapped IO ram") {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array_memmapped() {
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
test("memory mapped array not in mapped IO ram") {
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotValidRamC64_array_memmapped() {
|
||||
val address = 0xe000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
test("memory mapped array in mapped IO ram") {
|
||||
val address = 0xd800u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,116 +1,110 @@
|
||||
package prog8tests
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.closeTo
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.doubles.plusOrMinus
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestNumbers {
|
||||
@Test
|
||||
fun testToHex() {
|
||||
assertEquals("0", 0.toHex())
|
||||
assertEquals("1", 1.toHex())
|
||||
assertEquals("1", 1.234.toHex())
|
||||
assertEquals("10", 10.toHex())
|
||||
assertEquals("10", 10.99.toHex())
|
||||
assertEquals("15", 15.toHex())
|
||||
assertEquals("\$10", 16.toHex())
|
||||
assertEquals("\$ff", 255.toHex())
|
||||
assertEquals("\$0100", 256.toHex())
|
||||
assertEquals("\$4e5c", 20060.toHex())
|
||||
assertEquals("\$c382", 50050.toHex())
|
||||
assertEquals("\$ffff", 65535.toHex())
|
||||
assertEquals("\$ffff", 65535L.toHex())
|
||||
assertEquals("0", 0.toHex())
|
||||
assertEquals("-1", (-1).toHex())
|
||||
assertEquals("-1", (-1.234).toHex())
|
||||
assertEquals("-10", (-10).toHex())
|
||||
assertEquals("-10", (-10.99).toHex())
|
||||
assertEquals("-15", (-15).toHex())
|
||||
assertEquals("-\$10", (-16).toHex())
|
||||
assertEquals("-\$ff", (-255).toHex())
|
||||
assertEquals("-\$0100", (-256).toHex())
|
||||
assertEquals("-\$4e5c", (-20060).toHex())
|
||||
assertEquals("-\$c382", (-50050).toHex())
|
||||
assertEquals("-\$ffff", (-65535).toHex())
|
||||
assertEquals("-\$ffff", (-65535L).toHex())
|
||||
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
|
||||
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
|
||||
|
||||
class TestNumbers: FunSpec({
|
||||
test("testToHex") {
|
||||
0.toHex() shouldBe "0"
|
||||
1.toHex() shouldBe "1"
|
||||
1.234.toHex() shouldBe "1"
|
||||
10.toHex() shouldBe "10"
|
||||
10.99.toHex() shouldBe "10"
|
||||
15.toHex() shouldBe "15"
|
||||
16.toHex() shouldBe "\$10"
|
||||
255.toHex() shouldBe "\$ff"
|
||||
256.toHex() shouldBe "\$0100"
|
||||
20060.toHex() shouldBe "\$4e5c"
|
||||
50050.toHex() shouldBe "\$c382"
|
||||
65535.toHex() shouldBe "\$ffff"
|
||||
65535L.toHex() shouldBe "\$ffff"
|
||||
0.toHex() shouldBe "0"
|
||||
(-1).toHex() shouldBe "-1"
|
||||
(-1.234).toHex() shouldBe "-1"
|
||||
(-10).toHex() shouldBe "-10"
|
||||
(-10.99).toHex() shouldBe "-10"
|
||||
(-15).toHex() shouldBe "-15"
|
||||
(-16).toHex() shouldBe "-\$10"
|
||||
(-255).toHex() shouldBe "-\$ff"
|
||||
(-256).toHex() shouldBe "-\$0100"
|
||||
(-20060).toHex() shouldBe "-\$4e5c"
|
||||
(-50050).toHex() shouldBe "-\$c382"
|
||||
(-65535).toHex() shouldBe "-\$ffff"
|
||||
(-65535L).toHex() shouldBe "-\$ffff"
|
||||
shouldThrow<IllegalArgumentException> { 65536.toHex() }
|
||||
shouldThrow<IllegalArgumentException> { 65536L.toHex() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatToMflpt5() {
|
||||
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)))
|
||||
assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)))
|
||||
assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)))
|
||||
assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)))
|
||||
assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)))
|
||||
assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)))
|
||||
assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)))
|
||||
assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)))
|
||||
assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)))
|
||||
assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)))
|
||||
assertThat(Mflpt5.fromNumber(6.283185307179586), equalTo(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)))
|
||||
assertThat(Mflpt5.fromNumber(.25), equalTo(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(123.45678e22), equalTo(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)))
|
||||
assertThat(Mflpt5.fromNumber(-123.45678e-22), equalTo(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)))
|
||||
test("testFloatToMflpt5") {
|
||||
Mflpt5.fromNumber(0) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(3.141592653) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u)
|
||||
Mflpt5.fromNumber(3.141592653589793) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
|
||||
Mflpt5.fromNumber(32768) shouldBe Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(-32768) shouldBe Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(1) shouldBe Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(0.7071067812) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u)
|
||||
Mflpt5.fromNumber(0.7071067811865476) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u)
|
||||
Mflpt5.fromNumber(1.4142135624) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u)
|
||||
Mflpt5.fromNumber(1.4142135623730951) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u)
|
||||
Mflpt5.fromNumber(-.5) shouldBe Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(0.69314718061) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u)
|
||||
Mflpt5.fromNumber(0.6931471805599453) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u)
|
||||
Mflpt5.fromNumber(10) shouldBe Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(1000000000) shouldBe Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u)
|
||||
Mflpt5.fromNumber(.5) shouldBe Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(1.4426950408889634) shouldBe Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u)
|
||||
Mflpt5.fromNumber(1.5707963267948966) shouldBe Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
|
||||
Mflpt5.fromNumber(6.283185307179586) shouldBe Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
|
||||
Mflpt5.fromNumber(.25) shouldBe Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(123.45678e22) shouldBe Mflpt5(0xd1u, 0x02u, 0xb7u, 0x06u, 0xfbu)
|
||||
Mflpt5.fromNumber(-123.45678e-22) shouldBe Mflpt5(0x3eu, 0xe9u, 0x34u, 0x09u, 0x1bu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatRange() {
|
||||
assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)))
|
||||
assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)))
|
||||
assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)))
|
||||
assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
|
||||
assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
|
||||
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
|
||||
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
|
||||
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
|
||||
test("testFloatRange") {
|
||||
Mflpt5.fromNumber(FLOAT_MAX_POSITIVE) shouldBe Mflpt5(0xffu, 0x7fu, 0xffu, 0xffu, 0xffu)
|
||||
Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xffu, 0xffu, 0xffu, 0xffu, 0xffu)
|
||||
Mflpt5.fromNumber(1.7e-38) shouldBe Mflpt5(0x03u, 0x39u, 0x1du, 0x15u, 0x63u)
|
||||
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03u, 0xb9u, 0x1du, 0x15u, 0x63u)
|
||||
Mflpt5.fromNumber(-1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMflpt5ToFloat() {
|
||||
test("testMflpt5ToFloat") {
|
||||
val epsilon=0.000000001
|
||||
assertThat(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(0.0))
|
||||
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble(), closeTo(3.141592653, epsilon))
|
||||
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(3.141592653589793, epsilon))
|
||||
assertThat(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(32768.0))
|
||||
assertThat(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.0))
|
||||
assertThat(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(1.0))
|
||||
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, epsilon))
|
||||
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, epsilon))
|
||||
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, epsilon))
|
||||
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, epsilon))
|
||||
assertThat(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-.5))
|
||||
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, epsilon))
|
||||
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, epsilon))
|
||||
assertThat(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble(), equalTo(10.0))
|
||||
assertThat(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0))
|
||||
assertThat(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.5))
|
||||
assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, epsilon))
|
||||
assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, epsilon))
|
||||
assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, epsilon))
|
||||
assertThat(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.25))
|
||||
assertThat(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble(), closeTo(123.45678e22, 1.0e15))
|
||||
assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, epsilon))
|
||||
|
||||
Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 0.0
|
||||
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u).toDouble() shouldBe(3.141592653 plusOrMinus epsilon)
|
||||
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(3.141592653589793 plusOrMinus epsilon)
|
||||
Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 32768.0
|
||||
Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -32768.0
|
||||
Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 1.0
|
||||
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(0.7071067812 plusOrMinus epsilon)
|
||||
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(0.7071067811865476 plusOrMinus epsilon)
|
||||
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(1.4142135624 plusOrMinus epsilon)
|
||||
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(1.4142135623730951 plusOrMinus epsilon)
|
||||
Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -.5
|
||||
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u).toDouble() shouldBe(0.69314718061 plusOrMinus epsilon)
|
||||
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u).toDouble() shouldBe(0.6931471805599453 plusOrMinus epsilon)
|
||||
Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 10.0
|
||||
Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u).toDouble() shouldBe 1000000000.0
|
||||
Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .5
|
||||
Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u).toDouble() shouldBe(1.4426950408889634 plusOrMinus epsilon)
|
||||
Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(1.5707963267948966 plusOrMinus epsilon)
|
||||
Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(6.283185307179586 plusOrMinus epsilon)
|
||||
Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .25
|
||||
Mflpt5(0xd1u, 0x02u, 0xb7u, 0x06u, 0xfbu).toDouble() shouldBe(123.45678e22 plusOrMinus 1.0e15)
|
||||
Mflpt5(0x3eu, 0xe9u, 0x34u, 0x09u, 0x1bu).toDouble() shouldBe(-123.45678e-22 plusOrMinus epsilon)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,144 +1,153 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ExpressionError
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.ArrayLiteralValue
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestNumericLiteralValue {
|
||||
class TestNumericLiteralValue: FunSpec({
|
||||
|
||||
private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
|
||||
fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
|
||||
return lv1.type==lv2.type && lv1==lv2
|
||||
}
|
||||
|
||||
private val dummyPos = Position("test", 0, 0, 0)
|
||||
val dummyPos = Position("test", 0, 0, 0)
|
||||
|
||||
@Test
|
||||
fun testIdentity() {
|
||||
val v = NumericLiteralValue(DataType.UWORD, 12345, dummyPos)
|
||||
assertEquals(v, v)
|
||||
assertFalse(v != v)
|
||||
assertTrue(v <= v)
|
||||
assertTrue(v >= v)
|
||||
assertFalse(v < v)
|
||||
assertFalse(v > v)
|
||||
test("testIdentity") {
|
||||
val v = NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)
|
||||
(v==v) shouldBe true
|
||||
(v != v) shouldBe false
|
||||
(v <= v) shouldBe true
|
||||
(v >= v) shouldBe true
|
||||
(v < v ) shouldBe false
|
||||
(v > v ) shouldBe false
|
||||
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEqualsAndNotEquals() {
|
||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos))
|
||||
test("test rounding") {
|
||||
shouldThrow<ExpressionError> {
|
||||
NumericLiteralValue(DataType.BYTE, -2.345, dummyPos)
|
||||
}.message shouldContain "refused silent rounding"
|
||||
shouldThrow<ExpressionError> {
|
||||
NumericLiteralValue(DataType.BYTE, -2.6, dummyPos)
|
||||
}.message shouldContain "refused silent rounding"
|
||||
shouldThrow<ExpressionError> {
|
||||
NumericLiteralValue(DataType.UWORD, 2222.345, dummyPos)
|
||||
}.message shouldContain "refused silent rounding"
|
||||
NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos).number shouldBe 2.0
|
||||
NumericLiteralValue(DataType.BYTE, -2.0, dummyPos).number shouldBe -2.0
|
||||
NumericLiteralValue(DataType.UWORD, 2222.0, dummyPos).number shouldBe 2222.0
|
||||
NumericLiteralValue(DataType.FLOAT, 123.456, dummyPos)
|
||||
}
|
||||
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)))
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos)))
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)))
|
||||
test("testEqualsAndNotEquals") {
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 22239.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) == NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
|
||||
|
||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos))
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
|
||||
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 101.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 245.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 246.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 12346.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UBYTE, 9.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UWORD, 9.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)) shouldBe true
|
||||
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UWORD, 101.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 245.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 246.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12346.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)) shouldBe false
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEqualsRef() {
|
||||
assertEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("hello", false, dummyPos))
|
||||
assertNotEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("bye", false, dummyPos))
|
||||
assertEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", true, dummyPos))
|
||||
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("bye", true, dummyPos))
|
||||
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", false, dummyPos))
|
||||
test("testEqualsRef") {
|
||||
(StringLiteralValue("hello", false, dummyPos) == StringLiteralValue("hello", false, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", false, dummyPos) != StringLiteralValue("bye", false, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", true, dummyPos) == StringLiteralValue("hello", true, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("bye", true, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("hello", false, dummyPos)) shouldBe true
|
||||
|
||||
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
||||
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
||||
val lvThree = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
||||
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
||||
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
||||
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
||||
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
|
||||
val lvOne = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
|
||||
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
|
||||
val lvThree = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
|
||||
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
|
||||
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
|
||||
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
|
||||
val lvFour= NumericLiteralValue(DataType.UBYTE, 4.0, dummyPos)
|
||||
val lv1 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOne, lvTwo, lvThree), dummyPos)
|
||||
val lv2 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
|
||||
val lv3 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
|
||||
assertEquals(lv1, lv2)
|
||||
assertNotEquals(lv1, lv3)
|
||||
lv1 shouldBe lv2
|
||||
lv1 shouldNotBe lv3
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGreaterThan(){
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
|
||||
test("testGreaterThan") {
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 253.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe true
|
||||
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLessThan() {
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
|
||||
test("testLessThan") {
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 255.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe true
|
||||
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
@ -1,26 +1,34 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compilerinterface.*
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.*
|
||||
import prog8tests.helpers.generateAssembly
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestOptimization {
|
||||
@Test
|
||||
fun testRemoveEmptySubroutineExceptStart() {
|
||||
class TestOptimization: FunSpec({
|
||||
test("remove empty subroutine except start") {
|
||||
val sourcecode = """
|
||||
main {
|
||||
sub start() {
|
||||
@ -34,13 +42,16 @@ class TestOptimization {
|
||||
val toplevelModule = result.program.toplevelModule
|
||||
val mainBlock = toplevelModule.statements.single() as Block
|
||||
val startSub = mainBlock.statements.single() as Subroutine
|
||||
assertSame(result.program.entrypoint, startSub)
|
||||
assertEquals("start", startSub.name, "only start sub should remain")
|
||||
assertTrue(startSub.statements.single() is Return, "compiler has inserted return in empty subroutines")
|
||||
result.program.entrypoint shouldBeSameInstanceAs startSub
|
||||
withClue("only start sub should remain") {
|
||||
startSub.name shouldBe "start"
|
||||
}
|
||||
withClue("compiler has inserted return in empty subroutines") {
|
||||
startSub.statements.single() shouldBe instanceOf<Return>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDontRemoveEmptySubroutineIfItsReferenced() {
|
||||
test("don't remove empty subroutine if it's referenced") {
|
||||
val sourcecode = """
|
||||
main {
|
||||
sub start() {
|
||||
@ -57,45 +68,59 @@ class TestOptimization {
|
||||
val mainBlock = toplevelModule.statements.single() as Block
|
||||
val startSub = mainBlock.statements[0] as Subroutine
|
||||
val emptySub = mainBlock.statements[1] as Subroutine
|
||||
assertSame(result.program.entrypoint, startSub)
|
||||
assertEquals("start", startSub.name)
|
||||
assertEquals("empty", emptySub.name)
|
||||
assertTrue(emptySub.statements.single() is Return, "compiler has inserted return in empty subroutines")
|
||||
result.program.entrypoint shouldBeSameInstanceAs startSub
|
||||
startSub.name shouldBe "start"
|
||||
emptySub.name shouldBe "empty"
|
||||
withClue("compiler has inserted return in empty subroutines") {
|
||||
emptySub.statements.single() shouldBe instanceOf<Return>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGeneratedConstvalueInheritsProperParentLinkage()
|
||||
{
|
||||
val number = NumericLiteralValue(DataType.UBYTE, 11, Position.DUMMY)
|
||||
test("generated constvalue from typecast inherits proper parent linkage") {
|
||||
val number = NumericLiteralValue(DataType.UBYTE, 11.0, Position.DUMMY)
|
||||
val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
tc.linkParents(ParentSentinel)
|
||||
assertNotNull(tc.parent)
|
||||
assertNotNull(number.parent)
|
||||
assertSame(tc, number.parent)
|
||||
tc.parent shouldNotBe null
|
||||
number.parent shouldNotBe null
|
||||
tc shouldBeSameInstanceAs number.parent
|
||||
val constvalue = tc.constValue(program)!!
|
||||
assertIs<NumericLiteralValue>(constvalue)
|
||||
assertEquals(11, constvalue.number.toInt())
|
||||
assertEquals(DataType.BYTE, constvalue.type)
|
||||
assertSame(tc, constvalue.parent)
|
||||
constvalue shouldBe instanceOf<NumericLiteralValue>()
|
||||
constvalue.number shouldBe 11.0
|
||||
constvalue.type shouldBe DataType.BYTE
|
||||
constvalue.parent shouldBeSameInstanceAs tc.parent
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConstantFoldedAndSilentlyTypecastedForInitializerValues() {
|
||||
test("generated constvalue from prefixexpr inherits proper parent linkage") {
|
||||
val number = NumericLiteralValue(DataType.UBYTE, 11.0, Position.DUMMY)
|
||||
val pfx = PrefixExpression("-", number, Position.DUMMY)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
pfx.linkParents(ParentSentinel)
|
||||
pfx.parent shouldNotBe null
|
||||
number.parent shouldNotBe null
|
||||
pfx shouldBeSameInstanceAs number.parent
|
||||
val constvalue = pfx.constValue(program)!!
|
||||
constvalue shouldBe instanceOf<NumericLiteralValue>()
|
||||
constvalue.number shouldBe -11.0
|
||||
constvalue.type shouldBe DataType.BYTE
|
||||
constvalue.parent shouldBeSameInstanceAs pfx.parent
|
||||
}
|
||||
|
||||
test("constantfolded and silently typecasted for initializervalues") {
|
||||
val sourcecode = """
|
||||
main {
|
||||
sub start() {
|
||||
const ubyte TEST = 10
|
||||
byte x1 = TEST as byte + 1
|
||||
byte x2 = 1 + TEST as byte
|
||||
ubyte y1 = TEST + 1 as byte
|
||||
ubyte y2 = 1 as byte + TEST
|
||||
byte @shared x1 = TEST as byte + 1
|
||||
byte @shared x2 = 1 + TEST as byte
|
||||
ubyte @shared y1 = TEST + 1 as byte
|
||||
ubyte @shared y2 = 1 as byte + TEST
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
||||
val mainsub = result.program.entrypoint
|
||||
assertEquals(10, mainsub.statements.size)
|
||||
mainsub.statements.size shouldBe 10
|
||||
val declTest = mainsub.statements[0] as VarDecl
|
||||
val declX1 = mainsub.statements[1] as VarDecl
|
||||
val initX1 = mainsub.statements[2] as Assignment
|
||||
@ -105,19 +130,440 @@ class TestOptimization {
|
||||
val initY1 = mainsub.statements[6] as Assignment
|
||||
val declY2 = mainsub.statements[7] as VarDecl
|
||||
val initY2 = mainsub.statements[8] as Assignment
|
||||
assertIs<Return>(mainsub.statements[9])
|
||||
assertEquals(10.0, (declTest.value as NumericLiteralValue).number.toDouble())
|
||||
assertNull(declX1.value)
|
||||
assertNull(declX2.value)
|
||||
assertNull(declY1.value)
|
||||
assertNull(declY2.value)
|
||||
assertEquals(DataType.BYTE, (initX1.value as NumericLiteralValue).type)
|
||||
assertEquals(11.0, (initX1.value as NumericLiteralValue).number.toDouble())
|
||||
assertEquals(DataType.BYTE, (initX2.value as NumericLiteralValue).type)
|
||||
assertEquals(11.0, (initX2.value as NumericLiteralValue).number.toDouble())
|
||||
assertEquals(DataType.UBYTE, (initY1.value as NumericLiteralValue).type)
|
||||
assertEquals(11.0, (initY1.value as NumericLiteralValue).number.toDouble())
|
||||
assertEquals(DataType.UBYTE, (initY2.value as NumericLiteralValue).type)
|
||||
assertEquals(11.0, (initY2.value as NumericLiteralValue).number.toDouble())
|
||||
mainsub.statements[9] shouldBe instanceOf<Return>()
|
||||
(declTest.value as NumericLiteralValue).number shouldBe 10.0
|
||||
declX1.value shouldBe null
|
||||
declX2.value shouldBe null
|
||||
declY1.value shouldBe null
|
||||
declY2.value shouldBe null
|
||||
(initX1.value as NumericLiteralValue).type shouldBe DataType.BYTE
|
||||
(initX1.value as NumericLiteralValue).number shouldBe 11.0
|
||||
(initX2.value as NumericLiteralValue).type shouldBe DataType.BYTE
|
||||
(initX2.value as NumericLiteralValue).number shouldBe 11.0
|
||||
(initY1.value as NumericLiteralValue).type shouldBe DataType.UBYTE
|
||||
(initY1.value as NumericLiteralValue).number shouldBe 11.0
|
||||
(initY2.value as NumericLiteralValue).type shouldBe DataType.UBYTE
|
||||
(initY2.value as NumericLiteralValue).number shouldBe 11.0
|
||||
}
|
||||
}
|
||||
|
||||
test("typecasted assignment from ubyte logical expressoin to uword var") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte bb
|
||||
uword ww
|
||||
ww = not bb or not ww ; expression combining ubyte and uword
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
||||
|
||||
// ww = ((( not bb as uword) or not ww) as uword)
|
||||
val wwAssign = result.program.entrypoint.statements.last() as Assignment
|
||||
val expr = wwAssign.value as TypecastExpression
|
||||
|
||||
wwAssign.target.identifier?.nameInSource shouldBe listOf("ww")
|
||||
expr.type shouldBe DataType.UWORD
|
||||
expr.expression.inferType(result.program).istype(DataType.UBYTE) shouldBe true
|
||||
}
|
||||
|
||||
test("intermediate assignment steps have correct types for codegen phase (BeforeAsmGenerationAstChanger)") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte bb
|
||||
uword ww
|
||||
bb = not bb or not ww ; expression combining ubyte and uword
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
||||
|
||||
// bb = (( not bb as uword) or not ww)
|
||||
val bbAssign = result.program.entrypoint.statements.last() as Assignment
|
||||
val expr = bbAssign.value as BinaryExpression
|
||||
expr.operator shouldBe "or"
|
||||
expr.left shouldBe instanceOf<TypecastExpression>() // casted to word
|
||||
expr.right shouldBe instanceOf<PrefixExpression>()
|
||||
expr.left.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
expr.right.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target)
|
||||
val changer = BeforeAsmGenerationAstChanger(result.program,
|
||||
options,
|
||||
ErrorReporterForTests()
|
||||
)
|
||||
|
||||
changer.visit(result.program)
|
||||
while(changer.applyModifications()>0) {
|
||||
changer.visit(result.program)
|
||||
}
|
||||
|
||||
// assignment is now split into:
|
||||
// bb = not bb
|
||||
// bb = (bb or not ww)
|
||||
|
||||
val assigns = result.program.entrypoint.statements.filterIsInstance<Assignment>()
|
||||
val bbAssigns = assigns.filter { it.value !is NumericLiteralValue }
|
||||
bbAssigns.size shouldBe 2
|
||||
|
||||
bbAssigns[0].target.identifier!!.nameInSource shouldBe listOf("bb")
|
||||
bbAssigns[0].value shouldBe instanceOf<PrefixExpression>()
|
||||
(bbAssigns[0].value as PrefixExpression).operator shouldBe "not"
|
||||
(bbAssigns[0].value as PrefixExpression).expression shouldBe IdentifierReference(listOf("bb"), Position.DUMMY)
|
||||
bbAssigns[0].value.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
bbAssigns[1].target.identifier!!.nameInSource shouldBe listOf("bb")
|
||||
val bbAssigns1expr = bbAssigns[1].value as BinaryExpression
|
||||
bbAssigns1expr.operator shouldBe "or"
|
||||
bbAssigns1expr.left shouldBe IdentifierReference(listOf("bb"), Position.DUMMY)
|
||||
bbAssigns1expr.right shouldBe instanceOf<PrefixExpression>()
|
||||
(bbAssigns1expr.right as PrefixExpression).operator shouldBe "not"
|
||||
(bbAssigns1expr.right as PrefixExpression).expression shouldBe IdentifierReference(listOf("ww"), Position.DUMMY)
|
||||
bbAssigns1expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
val asm = generateAssembly(result.program, options)
|
||||
asm.valid shouldBe true
|
||||
}
|
||||
|
||||
test("intermediate assignment steps generated for typecasted expression") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte r
|
||||
ubyte @shared bb = (cos8(r)/2 + 100) as ubyte
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, true, src, writeAssembly = true).assertSuccess()
|
||||
/* turned into:
|
||||
ubyte r
|
||||
r = 0
|
||||
ubyte bb
|
||||
prog8_lib.retval_interm_b = cos8(r)
|
||||
prog8_lib.retval_interm_b >>= 1
|
||||
prog8_lib.retval_interm_b += 100
|
||||
bb = prog8_lib.retval_interm_b
|
||||
return
|
||||
*/
|
||||
val st = result.program.entrypoint.statements
|
||||
st.size shouldBe 8
|
||||
st.last() shouldBe instanceOf<Return>()
|
||||
var assign = st[3] as Assignment
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
|
||||
assign = st[4] as Assignment
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
|
||||
assign = st[5] as Assignment
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
|
||||
assign = st[6] as Assignment
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("bb")
|
||||
}
|
||||
|
||||
test("asmgen correctly deals with float typecasting in augmented assignment") {
|
||||
val src="""
|
||||
%option enable_floats
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
ubyte ub
|
||||
float ff = 1.0
|
||||
ff += (ub as float) ; operator doesn't matter
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=false, src, writeAssembly = false).assertSuccess()
|
||||
val assignFF = result.program.entrypoint.statements.last() as Assignment
|
||||
assignFF.isAugmentable shouldBe true
|
||||
assignFF.target.identifier!!.nameInSource shouldBe listOf("ff")
|
||||
val value = assignFF.value as BinaryExpression
|
||||
value.operator shouldBe "+"
|
||||
value.left shouldBe IdentifierReference(listOf("ff"), Position.DUMMY)
|
||||
value.right shouldBe instanceOf<TypecastExpression>()
|
||||
|
||||
val asm = generateAssembly(result.program)
|
||||
asm.valid shouldBe true
|
||||
}
|
||||
|
||||
test("unused variable removal") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte unused
|
||||
ubyte @shared unused_but_shared ; this one should remain
|
||||
ubyte usedvar_only_written
|
||||
usedvar_only_written=2
|
||||
usedvar_only_written++
|
||||
ubyte usedvar ; and this one too
|
||||
usedvar = msb(usedvar)
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
result.program.entrypoint.statements.size shouldBe 4 // unused_but_shared decl, unused_but_shared=0, usedvar decl, usedvar assign
|
||||
val (decl, assign, decl2, assign2) = result.program.entrypoint.statements
|
||||
decl shouldBe instanceOf<VarDecl>()
|
||||
(decl as VarDecl).name shouldBe "unused_but_shared"
|
||||
assign shouldBe instanceOf<Assignment>()
|
||||
decl2 shouldBe instanceOf<VarDecl>()
|
||||
(decl2 as VarDecl).name shouldBe "usedvar"
|
||||
assign2 shouldBe instanceOf<Assignment>()
|
||||
}
|
||||
|
||||
test("unused variable removal from subscope") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
if cx16.r0 {
|
||||
uword xx = 42 ; to be removed
|
||||
xx=99 ; to be removed
|
||||
cx16.r0 = 0
|
||||
}
|
||||
func2()
|
||||
|
||||
sub func2() {
|
||||
uword yy = 33 ; to be removed
|
||||
yy=99 ; to be removed
|
||||
cx16.r0 = 0
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
result.program.entrypoint.statements.size shouldBe 3
|
||||
val ifstmt = result.program.entrypoint.statements[0] as IfStatement
|
||||
ifstmt.truepart.statements.size shouldBe 1
|
||||
(ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
|
||||
val func2 = result.program.entrypoint.statements[2] as Subroutine
|
||||
func2.statements.size shouldBe 1
|
||||
(func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
|
||||
}
|
||||
|
||||
test("test simple augmented assignment optimization correctly initializes all variables") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared z1
|
||||
z1 = 10
|
||||
ubyte @shared z2
|
||||
z2 = ~z2
|
||||
ubyte @shared z3
|
||||
z3 = not z3
|
||||
uword @shared z4
|
||||
z4 = (z4 as ubyte)
|
||||
ubyte @shared z5
|
||||
z5 = z1+z5+5
|
||||
ubyte @shared z6
|
||||
z6 = z1+z6-5
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
/* expected:
|
||||
ubyte z1
|
||||
z1 = 10
|
||||
ubyte z2
|
||||
z2 = 255
|
||||
ubyte z3
|
||||
z3 = 1
|
||||
uword z4
|
||||
z4 = 0
|
||||
ubyte z5
|
||||
z5 = z1
|
||||
z5 += 5
|
||||
ubyte z6
|
||||
z6 = z1
|
||||
z6 -= 5
|
||||
*/
|
||||
val statements = result.program.entrypoint.statements
|
||||
statements.size shouldBe 14
|
||||
val z1decl = statements[0] as VarDecl
|
||||
val z1init = statements[1] as Assignment
|
||||
val z2decl = statements[2] as VarDecl
|
||||
val z2init = statements[3] as Assignment
|
||||
val z3decl = statements[4] as VarDecl
|
||||
val z3init = statements[5] as Assignment
|
||||
val z4decl = statements[6] as VarDecl
|
||||
val z4init = statements[7] as Assignment
|
||||
val z5decl = statements[8] as VarDecl
|
||||
val z5init = statements[9] as Assignment
|
||||
val z5plus = statements[10] as Assignment
|
||||
val z6decl = statements[11] as VarDecl
|
||||
val z6init = statements[12] as Assignment
|
||||
val z6plus = statements[13] as Assignment
|
||||
|
||||
z1decl.name shouldBe "z1"
|
||||
z1init.value shouldBe NumericLiteralValue(DataType.UBYTE, 10.0, Position.DUMMY)
|
||||
z2decl.name shouldBe "z2"
|
||||
z2init.value shouldBe NumericLiteralValue(DataType.UBYTE, 255.0, Position.DUMMY)
|
||||
z3decl.name shouldBe "z3"
|
||||
z3init.value shouldBe NumericLiteralValue(DataType.UBYTE, 1.0, Position.DUMMY)
|
||||
z4decl.name shouldBe "z4"
|
||||
z4init.value shouldBe NumericLiteralValue(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
z5decl.name shouldBe "z5"
|
||||
z5init.value shouldBe IdentifierReference(listOf("z1"), Position.DUMMY)
|
||||
z5plus.isAugmentable shouldBe true
|
||||
(z5plus.value as BinaryExpression).operator shouldBe "+"
|
||||
(z5plus.value as BinaryExpression).right shouldBe NumericLiteralValue(DataType.UBYTE, 5.0, Position.DUMMY)
|
||||
z6decl.name shouldBe "z6"
|
||||
z6init.value shouldBe IdentifierReference(listOf("z1"), Position.DUMMY)
|
||||
z6plus.isAugmentable shouldBe true
|
||||
(z6plus.value as BinaryExpression).operator shouldBe "-"
|
||||
(z6plus.value as BinaryExpression).right shouldBe NumericLiteralValue(DataType.UBYTE, 5.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("force_output option should work with optimizing memwrite assignment") {
|
||||
val src="""
|
||||
main {
|
||||
%option force_output
|
||||
|
||||
sub start() {
|
||||
uword aa
|
||||
ubyte zz
|
||||
@(aa) = zz + 32
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 6
|
||||
val assign=stmts.last() as Assignment
|
||||
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
|
||||
}
|
||||
|
||||
test("don't optimize memory writes away") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword aa
|
||||
ubyte zz
|
||||
@(aa) = zz + 32 ; do not optimize this away!
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 6
|
||||
val assign=stmts.last() as Assignment
|
||||
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
|
||||
}
|
||||
|
||||
test("correctly process constant prefix numbers") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared z1 = 1
|
||||
ubyte @shared z2 = + 1
|
||||
ubyte @shared z3 = ~ 1
|
||||
ubyte @shared z4 = not 1
|
||||
byte @shared z5 = - 1
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 10
|
||||
stmts.filterIsInstance<VarDecl>().size shouldBe 5
|
||||
stmts.filterIsInstance<Assignment>().size shouldBe 5
|
||||
}
|
||||
|
||||
test("correctly process constant prefix numbers with type mismatch and give error") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared z1 = - 1
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, optimize=true, src, writeAssembly=false, errors = errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "type of value BYTE doesn't match target UBYTE"
|
||||
errors.errors[1] shouldContain "value '-1' out of range for unsigned byte"
|
||||
}
|
||||
|
||||
test("test augmented expression asmgen") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte c
|
||||
ubyte r
|
||||
ubyte q
|
||||
r = (q+r)-c
|
||||
q=r
|
||||
r = q+(r-c)
|
||||
q=r
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, optimize=false, src, writeAssembly=true).assertSuccess()
|
||||
result.program.entrypoint.statements.size shouldBe 11
|
||||
result.program.entrypoint.statements.last() shouldBe instanceOf<Return>()
|
||||
}
|
||||
|
||||
test("keep the value initializer assignment if the next one depends on it") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared yy
|
||||
yy = 20 ; ok to remove =0 initializer before this
|
||||
uword @shared zz
|
||||
zz += 60 ; NOT ok to remove initializer, should evaluate to 60
|
||||
ubyte @shared xx
|
||||
xx = 6+sin8u(xx) ; NOT ok to remove initializer
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
/* expected result:
|
||||
uword yy
|
||||
yy = 20
|
||||
uword zz
|
||||
zz = 60
|
||||
ubyte xx
|
||||
xx = 0
|
||||
xx = sin8u(xx)
|
||||
xx += 6
|
||||
*/
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 8
|
||||
stmts.filterIsInstance<VarDecl>().size shouldBe 3
|
||||
stmts.filterIsInstance<Assignment>().size shouldBe 5
|
||||
}
|
||||
|
||||
test("only substitue assignments with 0 after a =0 initializer if it is the same variable") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared xx
|
||||
xx = xx + 20 ; is same var so can be changed just fine into xx=20
|
||||
uword @shared yy
|
||||
xx = 20
|
||||
yy = 0 ; is other var..
|
||||
xx = xx+10 ; so this should not be changed into xx=10
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
/*
|
||||
expected result:
|
||||
uword xx
|
||||
xx = 20
|
||||
uword yy
|
||||
yy = 0
|
||||
xx = 20
|
||||
yy = 0
|
||||
xx += 10
|
||||
*/
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 7
|
||||
stmts.filterIsInstance<VarDecl>().size shouldBe 2
|
||||
stmts.filterIsInstance<Assignment>().size shouldBe 5
|
||||
val assignXX1 = stmts[1] as Assignment
|
||||
assignXX1.target.identifier!!.nameInSource shouldBe listOf("xx")
|
||||
assignXX1.value shouldBe NumericLiteralValue(DataType.UBYTE, 20.0, Position.DUMMY)
|
||||
val assignXX2 = stmts.last() as Assignment
|
||||
assignXX2.target.identifier!!.nameInSource shouldBe listOf("xx")
|
||||
val xxValue = assignXX2.value as BinaryExpression
|
||||
xxValue.operator shouldBe "+"
|
||||
xxValue.left shouldBe IdentifierReference(listOf("xx"), Position.DUMMY)
|
||||
xxValue.right shouldBe NumericLiteralValue(DataType.UBYTE, 10.0, Position.DUMMY)
|
||||
}
|
||||
})
|
||||
|
@ -1,106 +1,83 @@
|
||||
package prog8tests
|
||||
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.expect
|
||||
import com.github.michaelbull.result.expectError
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import java.io.CharConversionException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestPetscii {
|
||||
class TestPetscii: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testZero() {
|
||||
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(Ok(listOf<Short>(0))))
|
||||
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(Ok(listOf<Short>(0))))
|
||||
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
|
||||
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
|
||||
test("testZero") {
|
||||
Petscii.encodePetscii("\u0000", true) shouldBe Ok(listOf<UByte>(0u))
|
||||
Petscii.encodePetscii("\u0000", false) shouldBe Ok(listOf<UByte>(0u))
|
||||
Petscii.decodePetscii(listOf(0u), true) shouldBe "\u0000"
|
||||
Petscii.decodePetscii(listOf(0u), false) shouldBe "\u0000"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLowercase() {
|
||||
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
|
||||
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
||||
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(Ok(listOf<Short>(0x12)))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("✓", true), equalTo(Ok(listOf<Short>(0xfa))))
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(Ok(listOf<Short>(255))))
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(Ok(listOf<Short>(0xd3))))
|
||||
test("testLowercase") {
|
||||
Petscii.encodePetscii("hello WORLD 123 @!£", true) shouldBe
|
||||
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 0xd7u, 0xcfu, 0xd2u, 0xccu, 0xc4u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||
Petscii.encodePetscii("\uf11a", true) shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||
Petscii.encodePetscii("✓", true) shouldBe Ok(listOf<UByte>(0xfau))
|
||||
withClue("expect lowercase error fallback") {
|
||||
Petscii.encodePetscii("π", true) shouldBe Ok(listOf<UByte>(255u))
|
||||
Petscii.encodePetscii("♥", true) shouldBe Ok(listOf<UByte>(0xd3u))
|
||||
}
|
||||
|
||||
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
|
||||
Petscii.decodePetscii(listOf(72u, 0xd7u, 0x5cu, 0xfau, 0x12u), true) shouldBe "hW£✓\uF11A"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUppercase() {
|
||||
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
|
||||
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
||||
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(Ok(listOf<Short>(0x12)))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("♥"), equalTo(Ok(listOf<Short>(0xd3))))
|
||||
assertThat(Petscii.encodePetscii("π"), equalTo(Ok(listOf<Short>(0xff))))
|
||||
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(Ok(listOf<Short>(250))))
|
||||
test("testUppercase") {
|
||||
Petscii.encodePetscii("HELLO 123 @!£") shouldBe
|
||||
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||
Petscii.encodePetscii("\uf11a") shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||
Petscii.encodePetscii("♥") shouldBe Ok(listOf<UByte>(0xd3u))
|
||||
Petscii.encodePetscii("π") shouldBe Ok(listOf<UByte>(0xffu))
|
||||
withClue("expecting fallback") {
|
||||
Petscii.encodePetscii("✓") shouldBe Ok(listOf<UByte>(250u))
|
||||
}
|
||||
|
||||
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
|
||||
Petscii.decodePetscii(listOf(72u, 0x5cu, 0xd3u, 0xffu)) shouldBe "H£♥π"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScreencodeLowercase() {
|
||||
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
|
||||
Ok(listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
|
||||
))
|
||||
assertThat(Petscii.encodeScreencode("✓", true), equalTo(Ok(listOf<Short>(0x7a))))
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(Ok(listOf<Short>(83))))
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(Ok(listOf<Short>(94))))
|
||||
test("testScreencodeLowercase") {
|
||||
Petscii.encodeScreencode("hello WORLD 123 @!£", true) shouldBe
|
||||
Ok(listOf<UByte>(0x08u, 0x05u, 0x0cu, 0x0cu, 0x0fu, 0x20u, 0x57u, 0x4fu, 0x52u, 0x4cu, 0x44u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||
Petscii.encodeScreencode("✓", true) shouldBe Ok(listOf<UByte>(0x7au))
|
||||
withClue("expect fallback") {
|
||||
Petscii.encodeScreencode("♥", true) shouldBe Ok(listOf<UByte>(83u))
|
||||
Petscii.encodeScreencode("π", true) shouldBe Ok(listOf<UByte>(94u))
|
||||
}
|
||||
|
||||
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
|
||||
Petscii.decodeScreencode(listOf(0x08u, 0x57u, 0x1cu, 0x7au), true) shouldBe "hW£✓"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScreencodeUppercase() {
|
||||
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
|
||||
Ok(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))))
|
||||
assertThat(Petscii.encodeScreencode("♥"), equalTo(Ok(listOf<Short>(0x53))))
|
||||
assertThat(Petscii.encodeScreencode("π"), equalTo(Ok(listOf<Short>(0x5e))))
|
||||
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(Ok(listOf<Short>(122))))
|
||||
test("testScreencodeUppercase") {
|
||||
Petscii.encodeScreencode("WORLD 123 @!£") shouldBe
|
||||
Ok(listOf<UByte>(0x17u, 0x0fu, 0x12u, 0x0cu, 0x04u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||
Petscii.encodeScreencode("♥") shouldBe Ok(listOf<UByte>(0x53u))
|
||||
Petscii.encodeScreencode("π") shouldBe Ok(listOf<UByte>(0x5eu))
|
||||
Petscii.encodeScreencode("HELLO") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||
withClue("expecting fallback") {
|
||||
Petscii.encodeScreencode("hello") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||
Petscii.encodeScreencode("✓") shouldBe Ok(listOf<UByte>(122u))
|
||||
}
|
||||
|
||||
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
|
||||
Petscii.decodeScreencode(listOf(0x17u, 0x1cu, 0x53u, 0x5eu)) shouldBe "W£♥π"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorCases() {
|
||||
test("testErrorCases") {
|
||||
Petscii.encodePetscii("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||
Petscii.encodePetscii("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||
Petscii.encodeScreencode("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||
Petscii.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||
|
||||
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), false) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), false) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), false) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), false) }
|
||||
|
||||
Petscii.scr2petscii(-1).expectError { "-1 should error" }
|
||||
Petscii.scr2petscii(256).expectError { "256 should error" }
|
||||
Petscii.petscii2scr(-1, true).expectError { "-1 should error" }
|
||||
Petscii.petscii2scr(256, true).expectError { "256 should error" }
|
||||
Petscii.petscii2scr(-1, false).expectError { "-1 should error" }
|
||||
Petscii.petscii2scr(256, false).expectError { "256 should error" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSpecialReplacements()
|
||||
{
|
||||
test("testSpecialReplacements") {
|
||||
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
|
||||
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
|
||||
|
||||
@ -109,92 +86,90 @@ class TestPetscii {
|
||||
Petscii.encodePetscii("~", false).expectError { "shouldn't have translation for tilde" }
|
||||
Petscii.encodePetscii("~", true).expectError { "shouldn't have translation for tilde" }
|
||||
|
||||
assertEquals(94, encodeP('^', false))
|
||||
assertEquals(94, encodeP('^', true))
|
||||
assertEquals(30, encodeS('^', false))
|
||||
assertEquals(30, encodeS('^', true))
|
||||
assertEquals(228, encodeP('_', false))
|
||||
assertEquals(228, encodeP('_', true))
|
||||
assertEquals(100, encodeS('_', false))
|
||||
assertEquals(100, encodeS('_', true))
|
||||
assertEquals(243, encodeP('{', false))
|
||||
assertEquals(243, encodeP('{', true))
|
||||
assertEquals(115, encodeS('{', false))
|
||||
assertEquals(115, encodeS('{', true))
|
||||
assertEquals(235, encodeP('}', false))
|
||||
assertEquals(235, encodeP('}', true))
|
||||
assertEquals(107, encodeS('}', false))
|
||||
assertEquals(107, encodeS('}', true))
|
||||
assertEquals(221, encodeP('|', false))
|
||||
assertEquals(221, encodeP('|', true))
|
||||
assertEquals(93, encodeS('|', false))
|
||||
assertEquals(93, encodeS('|', true))
|
||||
assertEquals(205, encodeP('\\', false))
|
||||
assertEquals(205, encodeP('\\', true))
|
||||
assertEquals(77, encodeS('\\', false))
|
||||
assertEquals(77, encodeS('\\', true))
|
||||
encodeP('^', false) shouldBe 94u
|
||||
encodeP('^', true) shouldBe 94u
|
||||
encodeS('^', false) shouldBe 30u
|
||||
encodeS('^', true) shouldBe 30u
|
||||
encodeP('_', false) shouldBe 228u
|
||||
encodeP('_', true) shouldBe 228u
|
||||
encodeS('_', false) shouldBe 100u
|
||||
encodeS('_', true) shouldBe 100u
|
||||
encodeP('{', false) shouldBe 243u
|
||||
encodeP('{', true) shouldBe 243u
|
||||
encodeS('{', false) shouldBe 115u
|
||||
encodeS('{', true) shouldBe 115u
|
||||
encodeP('}', false) shouldBe 235u
|
||||
encodeP('}', true) shouldBe 235u
|
||||
encodeS('}', false) shouldBe 107u
|
||||
encodeS('}', true) shouldBe 107u
|
||||
encodeP('|', false) shouldBe 221u
|
||||
encodeP('|', true) shouldBe 221u
|
||||
encodeS('|', false) shouldBe 93u
|
||||
encodeS('|', true) shouldBe 93u
|
||||
encodeP('\\', false) shouldBe 205u
|
||||
encodeP('\\', true) shouldBe 205u
|
||||
encodeS('\\', false) shouldBe 77u
|
||||
encodeS('\\', true) shouldBe 77u
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBoxDrawingCharsEncoding() {
|
||||
test("testBoxDrawingCharsEncoding") {
|
||||
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
|
||||
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
|
||||
|
||||
// pipe char
|
||||
assertEquals(221, encodeP('|', false))
|
||||
assertEquals(221, encodeP('|', true))
|
||||
assertEquals(93, encodeS('|', false))
|
||||
assertEquals(93, encodeS('|', true))
|
||||
encodeP('|', false) shouldBe 221u
|
||||
encodeP('|', true) shouldBe 221u
|
||||
encodeS('|', false) shouldBe 93u
|
||||
encodeS('|', true) shouldBe 93u
|
||||
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
|
||||
assertEquals(221, encodeP('│', false))
|
||||
assertEquals(221, encodeP('│', true))
|
||||
assertEquals(93, encodeS('│', false))
|
||||
assertEquals(93, encodeS('│', true))
|
||||
encodeP('│', false) shouldBe 221u
|
||||
encodeP('│', true) shouldBe 221u
|
||||
encodeS('│', false) shouldBe 93u
|
||||
encodeS('│', true) shouldBe 93u
|
||||
|
||||
// underscore
|
||||
assertEquals(228, encodeP('_', false))
|
||||
assertEquals(228, encodeP('_', true))
|
||||
assertEquals(100, encodeS('_', false))
|
||||
assertEquals(100, encodeS('_', true))
|
||||
encodeP('_', false) shouldBe 228u
|
||||
encodeP('_', true) shouldBe 228u
|
||||
encodeS('_', false) shouldBe 100u
|
||||
encodeS('_', true) shouldBe 100u
|
||||
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
|
||||
assertEquals(228, encodeP('▁', false))
|
||||
assertEquals(228, encodeP('▁', true))
|
||||
assertEquals(100, encodeS('▁', false))
|
||||
assertEquals(100, encodeS('▁', true))
|
||||
encodeP('▁', false) shouldBe 228u
|
||||
encodeP('▁', true) shouldBe 228u
|
||||
encodeS('▁', false) shouldBe 100u
|
||||
encodeS('▁', true) shouldBe 100u
|
||||
|
||||
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||
assertEquals(192, encodeP('─', false))
|
||||
assertEquals(192, encodeP('─', true))
|
||||
assertEquals(64, encodeS('─', false))
|
||||
assertEquals(64, encodeS('─', true))
|
||||
encodeP('─', false) shouldBe 192u
|
||||
encodeP('─', true) shouldBe 192u
|
||||
encodeS('─', false) shouldBe 64u
|
||||
encodeS('─', true) shouldBe 64u
|
||||
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
||||
assertEquals(221, encodeP('│', false))
|
||||
assertEquals(221, encodeP('│', true))
|
||||
assertEquals(93, encodeS('│', false))
|
||||
assertEquals(93, encodeS('│', true))
|
||||
encodeP('│', false) shouldBe 221u
|
||||
encodeP('│', true) shouldBe 221u
|
||||
encodeS('│', false) shouldBe 93u
|
||||
encodeS('│', true) shouldBe 93u
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBoxDrawingCharsDecoding() {
|
||||
test("testBoxDrawingCharsDecoding") {
|
||||
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||
assertEquals('\uf13b', Petscii.decodePetscii(listOf(195), false).single(), "BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)")
|
||||
assertEquals('C', Petscii.decodePetscii(listOf(195), true).single())
|
||||
assertEquals('─', Petscii.decodePetscii(listOf(192), false).single())
|
||||
assertEquals('─', Petscii.decodePetscii(listOf(192), true).single())
|
||||
assertEquals('\uf13b', Petscii.decodeScreencode(listOf(67), false).single(), "BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)")
|
||||
assertEquals('C', Petscii.decodeScreencode(listOf(67), true).single())
|
||||
assertEquals('─', Petscii.decodeScreencode(listOf(64), false).single())
|
||||
assertEquals('─', Petscii.decodeScreencode(listOf(64), true).single())
|
||||
Petscii.decodePetscii(listOf(195u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
|
||||
Petscii.decodePetscii(listOf(195u), true).single() shouldBe 'C'
|
||||
Petscii.decodePetscii(listOf(192u), false).single() shouldBe '─'
|
||||
Petscii.decodePetscii(listOf(192u), true).single() shouldBe '─'
|
||||
Petscii.decodeScreencode(listOf(67u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
|
||||
Petscii.decodeScreencode(listOf(67u), true).single() shouldBe 'C'
|
||||
Petscii.decodeScreencode(listOf(64u), false).single() shouldBe '─'
|
||||
Petscii.decodeScreencode(listOf(64u), true).single() shouldBe '─'
|
||||
|
||||
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
||||
assertEquals('│', Petscii.decodePetscii(listOf(125), false).single())
|
||||
assertEquals('│', Petscii.decodePetscii(listOf(125), true).single())
|
||||
assertEquals('│', Petscii.decodePetscii(listOf(221), false).single())
|
||||
assertEquals('│', Petscii.decodePetscii(listOf(221), true).single())
|
||||
assertEquals('│', Petscii.decodeScreencode(listOf(93), false).single())
|
||||
assertEquals('│', Petscii.decodeScreencode(listOf(93), true).single())
|
||||
assertEquals('\uf13c', Petscii.decodeScreencode(listOf(66), false).single(), "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)")
|
||||
assertEquals('B', Petscii.decodeScreencode(listOf(66), true).single())
|
||||
Petscii.decodePetscii(listOf(125u), false).single() shouldBe '│'
|
||||
Petscii.decodePetscii(listOf(125u), true).single() shouldBe '│'
|
||||
Petscii.decodePetscii(listOf(221u), false).single() shouldBe '│'
|
||||
Petscii.decodePetscii(listOf(221u), true).single() shouldBe '│'
|
||||
Petscii.decodeScreencode(listOf(93u), false).single() shouldBe '│'
|
||||
Petscii.decodeScreencode(listOf(93u), true).single() shouldBe '│'
|
||||
Petscii.decodeScreencode(listOf(66u), false).single() shouldBe '\uf13c' // "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)"
|
||||
Petscii.decodeScreencode(listOf(66u), true).single() shouldBe 'B'
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
@ -1,22 +1,25 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
||||
import prog8.ast.GlobalNamespace
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestScoping {
|
||||
class TestScoping: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testModulesParentIsGlobalNamespace() {
|
||||
test("modules parent is global namespace") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
@ -26,13 +29,12 @@ class TestScoping {
|
||||
|
||||
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
||||
val module = result.program.toplevelModule
|
||||
assertIs<GlobalNamespace>(module.parent)
|
||||
assertSame(result.program, module.program)
|
||||
assertIs<ParentSentinel>(module.parent.parent)
|
||||
module.parent shouldBe instanceOf<GlobalNamespace>()
|
||||
module.program shouldBeSameInstanceAs result.program
|
||||
module.parent.parent shouldBe instanceOf<ParentSentinel>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnonScopeVarsMovedIntoSubroutineScope() {
|
||||
test("anon scope vars moved into subroutine scope") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
@ -49,19 +51,28 @@ class TestScoping {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val start = mainBlock.statements.single() as Subroutine
|
||||
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
|
||||
assertFalse(mainBlock.statements.any { it is VarDecl }, "no vars moved to main block")
|
||||
withClue("no vars moved to main block") {
|
||||
mainBlock.statements.any { it is VarDecl } shouldBe false
|
||||
}
|
||||
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
|
||||
assertEquals(1, subroutineVars.size, "var from repeat anonscope must be moved up to subroutine")
|
||||
assertEquals("xx", subroutineVars[0].name)
|
||||
assertFalse(repeatbody.statements.any { it is VarDecl }, "var should have been removed from repeat anonscope")
|
||||
withClue("var from repeat anonscope must be moved up to subroutine") {
|
||||
subroutineVars.size shouldBe 1
|
||||
}
|
||||
subroutineVars[0].name shouldBe "xx"
|
||||
withClue("var should have been removed from repeat anonscope") {
|
||||
repeatbody.statements.any { it is VarDecl } shouldBe false
|
||||
}
|
||||
val initassign = repeatbody.statements[0] as? Assignment
|
||||
assertEquals(listOf("xx"), initassign?.target?.identifier?.nameInSource, "vardecl in repeat should be replaced by init assignment")
|
||||
assertEquals(99, (initassign?.value as? NumericLiteralValue)?.number?.toInt(), "vardecl in repeat should be replaced by init assignment")
|
||||
assertTrue(repeatbody.statements[1] is PostIncrDecr)
|
||||
withClue("vardecl in repeat should be replaced by init assignment") {
|
||||
initassign?.target?.identifier?.nameInSource shouldBe listOf("xx")
|
||||
}
|
||||
withClue("vardecl in repeat should be replaced by init assignment") {
|
||||
(initassign?.value as? NumericLiteralValue)?.number?.toInt() shouldBe 99
|
||||
}
|
||||
repeatbody.statements[1] shouldBe instanceOf<PostIncrDecr>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLabelsWithAnonScopes() {
|
||||
test("labels with anon scopes") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
@ -74,11 +85,9 @@ class TestScoping {
|
||||
addr = &labelinside
|
||||
addr = &labeloutside
|
||||
addr = &main.start.nested.nestedlabel
|
||||
addr = &nested.nestedlabel
|
||||
goto labeloutside
|
||||
goto iflabel
|
||||
goto main.start.nested.nestedlabel
|
||||
goto nested.nestedlabel
|
||||
}
|
||||
iflabel:
|
||||
}
|
||||
@ -88,11 +97,9 @@ class TestScoping {
|
||||
addr = &labelinside
|
||||
addr = &labeloutside
|
||||
addr = &main.start.nested.nestedlabel
|
||||
addr = &nested.nestedlabel
|
||||
goto iflabel
|
||||
goto labelinside
|
||||
goto main.start.nested.nestedlabel
|
||||
goto nested.nestedlabel
|
||||
labelinside:
|
||||
}
|
||||
|
||||
@ -108,9 +115,7 @@ class TestScoping {
|
||||
addr = &labelinside
|
||||
addr = &labeloutside
|
||||
addr = &main.start.nested.nestedlabel
|
||||
addr = &nested.nestedlabel
|
||||
goto main.start.nested.nestedlabel
|
||||
goto nested.nestedlabel
|
||||
}
|
||||
}
|
||||
"""
|
||||
@ -120,7 +125,211 @@ class TestScoping {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val start = mainBlock.statements.single() as Subroutine
|
||||
val labels = start.statements.filterIsInstance<Label>()
|
||||
assertEquals(1, labels.size, "only one label in subroutine scope")
|
||||
withClue("only one label in subroutine scope") {
|
||||
labels.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
test("good subroutine call without qualified names") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
routine()
|
||||
routine2()
|
||||
|
||||
sub routine2() {
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
start()
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
}
|
||||
|
||||
test("wrong subroutine call without qualified names") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
sub routine2() {
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
routine2()
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors= ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors = errors).assertFailure()
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "undefined symbol: routine2"
|
||||
}
|
||||
|
||||
test("good subroutine calls with qualified names (from root)") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
main.routine()
|
||||
main.start.routine2()
|
||||
|
||||
sub routine2() {
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
main.start.routine2()
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
}
|
||||
|
||||
test("wrong subroutine calls with qualified names (not from root)") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
start.routine2()
|
||||
wrong.start.routine2()
|
||||
sub routine2() {
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
start.routine2()
|
||||
wrong.start.routine2()
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors= ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 4
|
||||
errors.errors[0] shouldContain "undefined symbol: start.routine2"
|
||||
errors.errors[1] shouldContain "undefined symbol: wrong.start.routine2"
|
||||
errors.errors[2] shouldContain "undefined symbol: start.routine2"
|
||||
errors.errors[3] shouldContain "undefined symbol: wrong.start.routine2"
|
||||
}
|
||||
|
||||
test("good variables without qualified names") {
|
||||
val text="""
|
||||
main {
|
||||
ubyte v1
|
||||
|
||||
sub start() {
|
||||
ubyte v2
|
||||
v1=1
|
||||
v2=2
|
||||
|
||||
sub routine2() {
|
||||
ubyte v3
|
||||
v1=1
|
||||
v2=2
|
||||
v3=3
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
ubyte v4
|
||||
v1=1
|
||||
v4=4
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
}
|
||||
|
||||
test("wrong variables without qualified names") {
|
||||
val text="""
|
||||
main {
|
||||
ubyte v1
|
||||
|
||||
sub start() {
|
||||
ubyte v2
|
||||
v1=1
|
||||
v2=2
|
||||
v3=3 ; can't access
|
||||
v4=4 ; can't access
|
||||
sub routine2() {
|
||||
ubyte v3
|
||||
v1=1
|
||||
v2=2
|
||||
v3=3
|
||||
v4=3 ;can't access
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
ubyte v4
|
||||
v1=1
|
||||
v2=2 ; can't access
|
||||
v3=3 ; can't access
|
||||
v4=4
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors= ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 5
|
||||
errors.errors[0] shouldContain "undefined symbol: v3"
|
||||
errors.errors[1] shouldContain "undefined symbol: v4"
|
||||
errors.errors[2] shouldContain "undefined symbol: v4"
|
||||
errors.errors[3] shouldContain "undefined symbol: v2"
|
||||
errors.errors[4] shouldContain "undefined symbol: v3"
|
||||
}
|
||||
|
||||
test("good variable refs with qualified names (from root)") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
uword xx
|
||||
xx = &main.routine
|
||||
main.routine(5)
|
||||
main.routine.value = 5
|
||||
main.routine.arg = 5
|
||||
xx = &main.routine.nested
|
||||
main.routine.nested(5)
|
||||
main.routine.nested.nestedvalue = 5
|
||||
main.routine.nested.arg2 = 5
|
||||
}
|
||||
|
||||
sub routine(ubyte arg) {
|
||||
ubyte value
|
||||
|
||||
sub nested(ubyte arg2) {
|
||||
ubyte nestedvalue
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
}
|
||||
|
||||
test("wrong variable refs with qualified names 1 (not from root)") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
uword xx
|
||||
xx = &routine
|
||||
routine(5)
|
||||
routine.value = 5
|
||||
routine.arg = 5
|
||||
routine.nested.arg2 = 5
|
||||
routine.nested.nestedvalue = 5
|
||||
nested.nestedvalue = 5
|
||||
}
|
||||
|
||||
sub routine(ubyte arg) {
|
||||
ubyte value
|
||||
|
||||
sub nested(ubyte arg2) {
|
||||
ubyte nestedvalue
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors= ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 5
|
||||
errors.errors[0] shouldContain "undefined symbol: routine.value"
|
||||
errors.errors[1] shouldContain "undefined symbol: routine.arg"
|
||||
errors.errors[2] shouldContain "undefined symbol: routine.nested.arg2"
|
||||
errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue"
|
||||
errors.errors[4] shouldContain "undefined symbol: nested.nestedvalue"
|
||||
}
|
||||
})
|
||||
|
@ -1,8 +1,11 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
@ -11,14 +14,11 @@ import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestSubroutines {
|
||||
class TestSubroutines: FunSpec({
|
||||
|
||||
@Test
|
||||
fun stringParameter() {
|
||||
test("stringParameter") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
@ -46,27 +46,31 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.isEmpty())
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, func.parameters.single().type)
|
||||
assertEquals(4, func.statements.size)
|
||||
val paramvar = func.statements[0] as VarDecl
|
||||
assertEquals("thing", paramvar.name)
|
||||
assertEquals(DataType.STR, paramvar.datatype)
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.STR
|
||||
asmfunc.statements.isEmpty() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
withClue("str param for normal subroutine should be changed into UWORD") {
|
||||
func.parameters.single().type shouldBe DataType.UWORD
|
||||
func.statements.size shouldBe 4
|
||||
val paramvar = func.statements[0] as VarDecl
|
||||
paramvar.name shouldBe "thing"
|
||||
paramvar.datatype shouldBe DataType.UWORD
|
||||
}
|
||||
val assign = func.statements[2] as Assignment
|
||||
assertEquals(listOf("t2"), assign.target.identifier!!.nameInSource)
|
||||
assertTrue(assign.value is TypecastExpression, "str param in function body should not be transformed by normal compiler steps")
|
||||
assertEquals(DataType.UWORD, (assign.value as TypecastExpression).type)
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("t2")
|
||||
withClue("str param in function body should have been transformed into just uword assignment") {
|
||||
assign.value shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
val call = func.statements[3] as FunctionCallStatement
|
||||
assertEquals("asmfunc", call.target.nameInSource.single())
|
||||
assertTrue(call.args.single() is IdentifierReference, "str param in function body should not be transformed by normal compiler steps")
|
||||
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
|
||||
call.target.nameInSource.single() shouldBe "asmfunc"
|
||||
withClue("str param in function body should not be transformed by normal compiler steps") {
|
||||
call.args.single() shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
(call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stringParameterAsmGen() {
|
||||
test("stringParameterAsmGen") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
@ -94,30 +98,37 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.single() is Return)
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.UWORD, func.parameters.single().type, "asmgen should have changed str to uword type")
|
||||
assertTrue(asmfunc.statements.last() is Return)
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.STR
|
||||
asmfunc.statements.single() shouldBe instanceOf<Return>()
|
||||
func.isAsmSubroutine shouldBe false
|
||||
withClue("asmgen should have changed str to uword type") {
|
||||
func.parameters.single().type shouldBe DataType.UWORD
|
||||
}
|
||||
asmfunc.statements.last() shouldBe instanceOf<Return>()
|
||||
|
||||
assertEquals(5, func.statements.size)
|
||||
assertTrue(func.statements[4] is Return)
|
||||
func.statements.size shouldBe 5
|
||||
func.statements[4] shouldBe instanceOf<Return>()
|
||||
val paramvar = func.statements[0] as VarDecl
|
||||
assertEquals("thing", paramvar.name)
|
||||
assertEquals(DataType.UWORD, paramvar.datatype, "pre-asmgen should have changed str to uword type")
|
||||
paramvar.name shouldBe "thing"
|
||||
withClue("pre-asmgen should have changed str to uword type") {
|
||||
paramvar.datatype shouldBe DataType.UWORD
|
||||
}
|
||||
val assign = func.statements[2] as Assignment
|
||||
assertEquals(listOf("t2"), assign.target.identifier!!.nameInSource)
|
||||
assertTrue(assign.value is IdentifierReference, "str param in function body should be treated as plain uword before asmgen")
|
||||
assertEquals("thing", (assign.value as IdentifierReference).nameInSource.single())
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("t2")
|
||||
withClue("str param in function body should be treated as plain uword before asmgen") {
|
||||
assign.value shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
(assign.value as IdentifierReference).nameInSource.single() shouldBe "thing"
|
||||
val call = func.statements[3] as FunctionCallStatement
|
||||
assertEquals("asmfunc", call.target.nameInSource.single())
|
||||
assertTrue(call.args.single() is IdentifierReference, "str param in function body should be treated as plain uword and not been transformed")
|
||||
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
|
||||
call.target.nameInSource.single() shouldBe "asmfunc"
|
||||
withClue("str param in function body should be treated as plain uword and not been transformed") {
|
||||
call.args.single() shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
(call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun arrayParameterNotYetAllowed_ButShouldPerhapsBe() {
|
||||
test("array param not yet allowd (but should perhaps be?)") {
|
||||
// note: the *parser* accepts this as it is valid *syntax*,
|
||||
// however, it's not (yet) valid for the compiler
|
||||
val text = """
|
||||
@ -135,13 +146,12 @@ class TestSubroutines {
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, errors, false).assertFailure("currently array dt in signature is invalid") // TODO should not be invalid?
|
||||
assertEquals(0, errors.warnings.size)
|
||||
assertContains(errors.errors.single(), ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly")
|
||||
errors.warnings.size shouldBe 0
|
||||
errors.errors.single() shouldContain ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly"
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: allow array parameter in signature") // TODO allow this?
|
||||
fun arrayParameter() {
|
||||
// TODO allow this?
|
||||
xtest("arrayParameter") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
@ -170,23 +180,22 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.ARRAY_UB, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.isEmpty())
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
|
||||
assertTrue(func.statements.isEmpty())
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.ARRAY_UB
|
||||
asmfunc.statements.isEmpty() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
func.parameters.single().type shouldBe DataType.ARRAY_UB
|
||||
func.statements.isEmpty() shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsDirectMemoryRead() {
|
||||
test("uword param and normal varindexed as array work as DirectMemoryRead") {
|
||||
val text="""
|
||||
main {
|
||||
sub thing(uword rr) {
|
||||
ubyte xx = rr[1] ; should still work as var initializer that will be rewritten
|
||||
ubyte yy
|
||||
ubyte @shared xx = rr[1] ; should still work as var initializer that will be rewritten
|
||||
ubyte @shared yy
|
||||
yy = rr[2]
|
||||
uword other
|
||||
uword @shared other
|
||||
ubyte zz = other[3]
|
||||
}
|
||||
|
||||
@ -201,29 +210,30 @@ class TestSubroutines {
|
||||
val module = result.program.toplevelModule
|
||||
val block = module.statements.single() as Block
|
||||
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
|
||||
assertEquals("main", block.name)
|
||||
assertEquals(10, thing.statements.size, "rr, xx, xx assign, yy, yy assign, other, other assign 0, zz, zz assign, return")
|
||||
block.name shouldBe "main"
|
||||
thing.statements.size shouldBe 11 // rr paramdecl, xx, xx assign, yy decl, yy init 0, yy assign, other, other assign 0, zz, zz assign, return
|
||||
val xx = thing.statements[1] as VarDecl
|
||||
assertNull(xx.value, "vardecl init values must have been moved to separate assignments")
|
||||
withClue("vardecl init values must have been moved to separate assignments") {
|
||||
xx.value shouldBe null
|
||||
}
|
||||
val assignXX = thing.statements[2] as Assignment
|
||||
val assignYY = thing.statements[4] as Assignment
|
||||
val assignZZ = thing.statements[8] as Assignment
|
||||
assertEquals(listOf("xx"), assignXX.target.identifier!!.nameInSource)
|
||||
assertEquals(listOf("yy"), assignYY.target.identifier!!.nameInSource)
|
||||
assertEquals(listOf("zz"), assignZZ.target.identifier!!.nameInSource)
|
||||
val assignYY = thing.statements[5] as Assignment
|
||||
val assignZZ = thing.statements[9] as Assignment
|
||||
assignXX.target.identifier!!.nameInSource shouldBe listOf("xx")
|
||||
assignYY.target.identifier!!.nameInSource shouldBe listOf("yy")
|
||||
assignZZ.target.identifier!!.nameInSource shouldBe listOf("zz")
|
||||
val valueXXexpr = (assignXX.value as DirectMemoryRead).addressExpression as BinaryExpression
|
||||
val valueYYexpr = (assignYY.value as DirectMemoryRead).addressExpression as BinaryExpression
|
||||
val valueZZexpr = (assignZZ.value as DirectMemoryRead).addressExpression as BinaryExpression
|
||||
assertEquals(listOf("rr"), (valueXXexpr.left as IdentifierReference).nameInSource)
|
||||
assertEquals(listOf("rr"), (valueYYexpr.left as IdentifierReference).nameInSource)
|
||||
assertEquals(listOf("other"), (valueZZexpr.left as IdentifierReference).nameInSource)
|
||||
assertEquals(1, (valueXXexpr.right as NumericLiteralValue).number.toInt())
|
||||
assertEquals(2, (valueYYexpr.right as NumericLiteralValue).number.toInt())
|
||||
assertEquals(3, (valueZZexpr.right as NumericLiteralValue).number.toInt())
|
||||
(valueXXexpr.left as IdentifierReference).nameInSource shouldBe listOf("rr")
|
||||
(valueYYexpr.left as IdentifierReference).nameInSource shouldBe listOf("rr")
|
||||
(valueZZexpr.left as IdentifierReference).nameInSource shouldBe listOf("other")
|
||||
(valueXXexpr.right as NumericLiteralValue).number.toInt() shouldBe 1
|
||||
(valueYYexpr.right as NumericLiteralValue).number.toInt() shouldBe 2
|
||||
(valueZZexpr.right as NumericLiteralValue).number.toInt() shouldBe 3
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsMemoryWrite() {
|
||||
test("uword param and normal varindexed as array work as MemoryWrite") {
|
||||
val text="""
|
||||
main {
|
||||
sub thing(uword rr) {
|
||||
@ -241,15 +251,15 @@ class TestSubroutines {
|
||||
val module = result.program.toplevelModule
|
||||
val block = module.statements.single() as Block
|
||||
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
|
||||
assertEquals("main", block.name)
|
||||
assertEquals(3, thing.statements.size, "rr, rr assign, return void")
|
||||
block.name shouldBe "main"
|
||||
thing.statements.size shouldBe 3 // "rr, rr assign, return void"
|
||||
val assignRR = thing.statements[1] as Assignment
|
||||
assertEquals(42, (assignRR.value as NumericLiteralValue).number.toInt())
|
||||
(assignRR.value as NumericLiteralValue).number.toInt() shouldBe 42
|
||||
val memwrite = assignRR.target.memoryAddress
|
||||
assertNotNull(memwrite, "memwrite to set byte array value")
|
||||
val addressExpr = memwrite.addressExpression as BinaryExpression
|
||||
assertEquals(listOf("rr"), (addressExpr.left as IdentifierReference).nameInSource)
|
||||
assertEquals("+", addressExpr.operator)
|
||||
assertEquals(10, (addressExpr.right as NumericLiteralValue).number.toInt())
|
||||
memwrite shouldNotBe null
|
||||
val addressExpr = memwrite!!.addressExpression as BinaryExpression
|
||||
(addressExpr.left as IdentifierReference).nameInSource shouldBe listOf("rr")
|
||||
addressExpr.operator shouldBe "+"
|
||||
(addressExpr.right as NumericLiteralValue).number.toInt() shouldBe 10
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,50 +1,45 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeIn
|
||||
import io.kotest.matchers.collections.shouldNotBeIn
|
||||
import io.kotest.matchers.comparables.shouldBeGreaterThan
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
|
||||
import prog8.compilerinterface.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAbstractZeropage {
|
||||
|
||||
@Test
|
||||
fun testAbstractZeropage() {
|
||||
val compTarget = DummyCompilationTarget()
|
||||
val zp = DummyZeropage(
|
||||
CompilationOptions(
|
||||
OutputType.RAW,
|
||||
LauncherType.NONE,
|
||||
ZeropageType.FULL,
|
||||
listOf((0x50..0x5f)),
|
||||
false,
|
||||
false,
|
||||
compTarget
|
||||
)
|
||||
)
|
||||
assertEquals(256-6-16, zp.free.size)
|
||||
}
|
||||
class TestAbstractZeropage: FunSpec({
|
||||
|
||||
class DummyCompilationTarget: ICompilationTarget {
|
||||
override val name: String = "dummy"
|
||||
override val machine: IMachineDefinition
|
||||
get() = throw NotImplementedError("dummy")
|
||||
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
paramRegisters: List<RegisterOrStatusflag>): Boolean {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
@ -55,177 +50,184 @@ class TestAbstractZeropage {
|
||||
}
|
||||
|
||||
class DummyZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
override val SCRATCH_B1: Int = 0x10
|
||||
override val SCRATCH_REG: Int = 0x11
|
||||
override val SCRATCH_W1: Int= 0x20
|
||||
override val SCRATCH_W2: Int = 0x30
|
||||
override val SCRATCH_B1 = 0x10u
|
||||
override val SCRATCH_REG = 0x11u
|
||||
override val SCRATCH_W1 = 0x20u
|
||||
override val SCRATCH_W2 = 0x30u
|
||||
|
||||
init {
|
||||
free.addAll(0..255)
|
||||
free.addAll(0u..255u)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
test("testAbstractZeropage") {
|
||||
val compTarget = DummyCompilationTarget()
|
||||
val zp = DummyZeropage(
|
||||
CompilationOptions(
|
||||
OutputType.RAW,
|
||||
LauncherType.NONE,
|
||||
ZeropageType.FULL,
|
||||
listOf((0x50u..0x5fu)),
|
||||
false,
|
||||
false,
|
||||
compTarget
|
||||
)
|
||||
)
|
||||
zp.free.size shouldBe 256-6-16
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestC64Zeropage {
|
||||
class TestC64Zeropage: FunSpec({
|
||||
|
||||
private val errors = ErrorReporterForTests()
|
||||
val errors = ErrorReporterForTests()
|
||||
|
||||
@Test
|
||||
fun testNames() {
|
||||
test("testNames") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
||||
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||
assertFailsWith<AssertionError> {
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||
}
|
||||
zp.allocate("varname2", DataType.UBYTE, null, errors)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpFloatEnable() {
|
||||
test("testZpFloatEnable") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
shouldThrow<InternalCompilerException> {
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
shouldThrow<InternalCompilerException> {
|
||||
zp2.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
|
||||
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpModesWithFloats() {
|
||||
test("testZpModesWithFloats") {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
|
||||
}
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpDontuse() {
|
||||
test("testZpDontuse") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
|
||||
println(zp.free)
|
||||
assertEquals(0, zp.availableBytes())
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
zp.availableBytes() shouldBe 0
|
||||
shouldThrow<InternalCompilerException> {
|
||||
zp.allocate("", DataType.BYTE, null, errors)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpacesBytes() {
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
assertEquals(18, zp1.availableBytes())
|
||||
zp1.availableBytes() shouldBe 18
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(85, zp2.availableBytes())
|
||||
zp2.availableBytes() shouldBe 85
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(125, zp3.availableBytes())
|
||||
zp3.availableBytes() shouldBe 125
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertEquals(238, zp4.availableBytes())
|
||||
zp4.availableBytes() shouldBe 238
|
||||
zp4.allocate("test", DataType.UBYTE, null, errors)
|
||||
assertEquals(237, zp4.availableBytes())
|
||||
zp4.availableBytes() shouldBe 237
|
||||
zp4.allocate("test2", DataType.UBYTE, null, errors)
|
||||
assertEquals(236, zp4.availableBytes())
|
||||
zp4.availableBytes() shouldBe 236
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpacesWords() {
|
||||
test("testFreeSpacesWords") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
assertEquals(6, zp1.availableWords())
|
||||
zp1.availableWords() shouldBe 6
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(38, zp2.availableWords())
|
||||
zp2.availableWords() shouldBe 38
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(57, zp3.availableWords())
|
||||
zp3.availableWords() shouldBe 57
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertEquals(116, zp4.availableWords())
|
||||
zp4.availableWords() shouldBe 116
|
||||
zp4.allocate("test", DataType.UWORD, null, errors)
|
||||
assertEquals(115, zp4.availableWords())
|
||||
zp4.availableWords() shouldBe 115
|
||||
zp4.allocate("test2", DataType.UWORD, null, errors)
|
||||
assertEquals(114, zp4.availableWords())
|
||||
zp4.availableWords() shouldBe 114
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedSpace() {
|
||||
test("testReservedSpace") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertEquals(238, zp1.availableBytes())
|
||||
assertTrue(50 in zp1.free)
|
||||
assertTrue(100 in zp1.free)
|
||||
assertTrue(49 in zp1.free)
|
||||
assertTrue(101 in zp1.free)
|
||||
assertTrue(200 in zp1.free)
|
||||
assertTrue(255 in zp1.free)
|
||||
assertTrue(199 in zp1.free)
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
|
||||
assertEquals(139, zp2.availableBytes())
|
||||
assertFalse(50 in zp2.free)
|
||||
assertFalse(100 in zp2.free)
|
||||
assertTrue(49 in zp2.free)
|
||||
assertTrue(101 in zp2.free)
|
||||
assertFalse(200 in zp2.free)
|
||||
assertFalse(255 in zp2.free)
|
||||
assertTrue(199 in zp2.free)
|
||||
zp1.availableBytes() shouldBe 238
|
||||
50u shouldBeIn zp1.free
|
||||
100u shouldBeIn zp1.free
|
||||
49u shouldBeIn zp1.free
|
||||
101u shouldBeIn zp1.free
|
||||
200u shouldBeIn zp1.free
|
||||
255u shouldBeIn zp1.free
|
||||
199u shouldBeIn zp1.free
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, C64Target))
|
||||
zp2.availableBytes() shouldBe 139
|
||||
50u shouldNotBeIn zp2.free
|
||||
100u shouldNotBeIn zp2.free
|
||||
49u shouldBeIn zp2.free
|
||||
101u shouldBeIn zp2.free
|
||||
200u shouldNotBeIn zp2.free
|
||||
255u shouldNotBeIn zp2.free
|
||||
199u shouldBeIn zp2.free
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBasicsafeAllocation() {
|
||||
test("testBasicsafeAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
assertEquals(18, zp.availableBytes())
|
||||
assertTrue(zp.hasByteAvailable())
|
||||
assertTrue(zp.hasWordAvailable())
|
||||
zp.availableBytes() shouldBe 18
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
// in regular zp there aren't 5 sequential bytes free
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
|
||||
for (i in 0 until zp.availableBytes()) {
|
||||
val loc = zp.allocate("", DataType.UBYTE, null, errors)
|
||||
assertTrue(loc > 0)
|
||||
loc shouldBeGreaterThan 0u
|
||||
}
|
||||
assertEquals(0, zp.availableBytes())
|
||||
assertFalse(zp.hasByteAvailable())
|
||||
assertFalse(zp.hasWordAvailable())
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
zp.availableBytes() shouldBe 0
|
||||
zp.hasByteAvailable() shouldBe false
|
||||
zp.hasWordAvailable() shouldBe false
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFullAllocation() {
|
||||
test("testFullAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertEquals(238, zp.availableBytes())
|
||||
assertTrue(zp.hasByteAvailable())
|
||||
assertTrue(zp.hasWordAvailable())
|
||||
zp.availableBytes() shouldBe 238
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||
assertTrue(loc > 3)
|
||||
assertFalse(loc in zp.free)
|
||||
loc shouldBeGreaterThan 3u
|
||||
loc shouldNotBeIn zp.free
|
||||
val num = zp.availableBytes() / 2
|
||||
|
||||
for(i in 0..num-4) {
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
assertEquals(6,zp.availableBytes())
|
||||
zp.availableBytes() shouldBe 6
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
// can't allocate because no more sequential bytes, only fragmented
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
@ -234,88 +236,85 @@ class TestC64Zeropage {
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
|
||||
assertEquals(0, zp.availableBytes())
|
||||
assertFalse(zp.hasByteAvailable())
|
||||
assertFalse(zp.hasWordAvailable())
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
zp.availableBytes() shouldBe 0
|
||||
zp.hasByteAvailable() shouldBe false
|
||||
zp.hasWordAvailable() shouldBe false
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
// no more space
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEfficientAllocation() {
|
||||
test("testEfficientAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
assertEquals(18, zp.availableBytes())
|
||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x9b, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0, zp.availableBytes())
|
||||
zp.availableBytes() shouldBe 18
|
||||
zp.allocate("", DataType.WORD, null, errors) shouldBe 0x04u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0au
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9bu
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9eu
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5u
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0u
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbeu
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0eu
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9u
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedLocations() {
|
||||
test("testReservedLocations") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCx16Zeropage {
|
||||
private val errors = ErrorReporterForTests()
|
||||
class TestCx16Zeropage: FunSpec({
|
||||
val errors = ErrorReporterForTests()
|
||||
|
||||
@Test
|
||||
fun testReservedLocations() {
|
||||
test("testReservedLocations") {
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpacesBytes() {
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
|
||||
assertEquals(88, zp1.availableBytes())
|
||||
zp1.availableBytes() shouldBe 88
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(175, zp2.availableBytes())
|
||||
zp2.availableBytes() shouldBe 175
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(216, zp3.availableBytes())
|
||||
zp3.availableBytes() shouldBe 216
|
||||
zp3.allocate("test", DataType.UBYTE, null, errors)
|
||||
assertEquals(215, zp3.availableBytes())
|
||||
zp3.availableBytes() shouldBe 215
|
||||
zp3.allocate("test2", DataType.UBYTE, null, errors)
|
||||
assertEquals(214, zp3.availableBytes())
|
||||
zp3.availableBytes() shouldBe 214
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpacesWords() {
|
||||
test("testFreeSpacesWords") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(108, zp1.availableWords())
|
||||
zp1.availableWords() shouldBe 108
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(87, zp2.availableWords())
|
||||
zp2.availableWords() shouldBe 87
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
|
||||
assertEquals(44, zp3.availableWords())
|
||||
zp3.availableWords() shouldBe 44
|
||||
zp3.allocate("test", DataType.UWORD, null, errors)
|
||||
assertEquals(43, zp3.availableWords())
|
||||
zp3.availableWords() shouldBe 43
|
||||
zp3.allocate("test2", DataType.UWORD, null, errors)
|
||||
assertEquals(42, zp3.availableWords())
|
||||
zp3.availableWords() shouldBe 42
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedSpace() {
|
||||
test("testReservedSpace") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(216, zp1.availableBytes())
|
||||
assertTrue(0x22 in zp1.free)
|
||||
assertTrue(0x80 in zp1.free)
|
||||
assertTrue(0xff in zp1.free)
|
||||
assertFalse(0x02 in zp1.free)
|
||||
assertFalse(0x21 in zp1.free)
|
||||
zp1.availableBytes() shouldBe 216
|
||||
0x22u shouldBeIn zp1.free
|
||||
0x80u shouldBeIn zp1.free
|
||||
0xffu shouldBeIn zp1.free
|
||||
0x02u shouldNotBeIn zp1.free
|
||||
0x21u shouldNotBeIn zp1.free
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -22,15 +22,15 @@ internal val DummyFunctions = object : IBuiltinFunctions {
|
||||
}
|
||||
|
||||
internal val DummyMemsizer = object : IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int = 0
|
||||
override fun memorySize(dt: DataType) = 0
|
||||
}
|
||||
|
||||
internal val DummyStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
|
||||
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
|
@ -1,24 +1,32 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compilerinterface.*
|
||||
import prog8tests.ast.helpers.assumeReadableFile
|
||||
import prog8tests.ast.helpers.outputDir
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.name
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
internal fun CompilationResult.assertSuccess(description: String = ""): CompilationResult {
|
||||
assertTrue(success, "expected successful compilation but failed $description")
|
||||
withClue("expected successful compilation but failed $description") {
|
||||
success shouldBe true
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun CompilationResult.assertFailure(description: String = ""): CompilationResult {
|
||||
assertFalse(success, "expected failure to compile but succeeded $description")
|
||||
withClue("expected failure to compile but succeeded $description") {
|
||||
success shouldBe false
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@ -33,22 +41,23 @@ internal fun compileFile(
|
||||
fileName: String,
|
||||
outputDir: Path = prog8tests.ast.helpers.outputDir,
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true
|
||||
writeAssembly: Boolean = true,
|
||||
optFloatExpr: Boolean = true
|
||||
) : CompilationResult {
|
||||
val filepath = fileDir.resolve(fileName)
|
||||
assumeReadableFile(filepath)
|
||||
return compileProgram(
|
||||
val args = CompilerArguments(
|
||||
filepath,
|
||||
optimize,
|
||||
optimizeFloatExpressions = false,
|
||||
optimizeFloatExpressions = optFloatExpr,
|
||||
writeAssembly = writeAssembly,
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
platform.name,
|
||||
sourceDirs = listOf(),
|
||||
outputDir,
|
||||
outputDir = outputDir,
|
||||
errors = errors ?: ErrorReporterForTests()
|
||||
)
|
||||
return compileProgram(args)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,10 +70,23 @@ internal fun compileText(
|
||||
optimize: Boolean,
|
||||
sourceText: String,
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true
|
||||
writeAssembly: Boolean = true,
|
||||
optFloatExpr: Boolean = true
|
||||
) : CompilationResult {
|
||||
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
|
||||
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
|
||||
filePath.toFile().writeText(sourceText)
|
||||
return compileFile(platform, optimize, filePath.parent, filePath.name, errors=errors, writeAssembly=writeAssembly)
|
||||
return compileFile(platform, optimize, filePath.parent, filePath.name, errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr)
|
||||
}
|
||||
|
||||
|
||||
internal fun generateAssembly(
|
||||
program: Program,
|
||||
options: CompilationOptions? = null
|
||||
): IAssemblyProgram {
|
||||
val coptions = options ?: CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, true, C64Target)
|
||||
val zp = C64MachineDefinition.C64Zeropage(coptions)
|
||||
coptions.compTarget.machine.zeropage=zp
|
||||
val asmgen = AsmGen(program, ErrorReporterForTests(), zp, coptions, C64Target, outputDir)
|
||||
return asmgen.compileToAssembly()
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id "java"
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id "io.kotest" version "0.3.8"
|
||||
}
|
||||
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
@ -15,10 +15,7 @@ dependencies {
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
implementation project(':parser')
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
@ -42,7 +39,6 @@ sourceSets {
|
||||
}
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
|
@ -13,8 +13,8 @@
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="parser" />
|
||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||
<orderEntry type="library" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -219,7 +219,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(jump: Jump) {
|
||||
output("goto ")
|
||||
if(jump.isGosub)
|
||||
output("gosub ")
|
||||
else
|
||||
output("goto ")
|
||||
when {
|
||||
jump.address!=null -> output(jump.address.toHex())
|
||||
jump.generatedLabel!=null -> output(jump.generatedLabel)
|
||||
@ -262,7 +265,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(numLiteral: NumericLiteralValue) {
|
||||
output(numLiteral.number.toString())
|
||||
if(numLiteral.type==DataType.FLOAT)
|
||||
output(numLiteral.number.toString())
|
||||
else
|
||||
output(numLiteral.number.toInt().toString())
|
||||
}
|
||||
|
||||
override fun visit(char: CharLiteral) {
|
||||
|
@ -68,6 +68,9 @@ interface IStatementContainer {
|
||||
fun isNotEmpty(): Boolean = statements.isNotEmpty()
|
||||
|
||||
fun searchSymbol(name: String): Statement? {
|
||||
if(this is Subroutine && isAsmSubroutine)
|
||||
return searchAsmParameter(name)
|
||||
|
||||
// this is called quite a lot and could perhaps be optimized a bit more,
|
||||
// but adding a memoization cache didn't make much of a practical runtime difference...
|
||||
for (stmt in statements) {
|
||||
@ -75,16 +78,10 @@ interface IStatementContainer {
|
||||
// is INamedStatement -> {
|
||||
// if(stmt.name==name) return stmt
|
||||
// }
|
||||
is VarDecl -> {
|
||||
if(stmt.name==name) return stmt
|
||||
}
|
||||
is Label -> {
|
||||
if(stmt.name==name) return stmt
|
||||
}
|
||||
is Subroutine -> {
|
||||
if(stmt.name==name)
|
||||
return stmt
|
||||
}
|
||||
is VarDecl -> if(stmt.name==name) return stmt
|
||||
is Label -> if(stmt.name==name) return stmt
|
||||
is Subroutine -> if(stmt.name==name) return stmt
|
||||
is Block -> if(stmt.name==name) return stmt
|
||||
is AnonymousScope -> {
|
||||
val found = stmt.searchSymbol(name)
|
||||
if(found!=null)
|
||||
@ -154,41 +151,22 @@ interface INameScope: IStatementContainer, INamedStatement {
|
||||
}
|
||||
|
||||
private fun lookupQualified(scopedName: List<String>): Statement? {
|
||||
// a scoped name refers to a name in another namespace.
|
||||
// look "up" from our current scope to search for the correct one.
|
||||
val localScope = this.subScope(scopedName[0])
|
||||
if(localScope!=null)
|
||||
return resolveLocally(localScope, scopedName.drop(1))
|
||||
|
||||
var statementScope = this
|
||||
while(statementScope !is GlobalNamespace) {
|
||||
if(statementScope !is Module && statementScope.name==scopedName[0]) {
|
||||
return resolveLocally(statementScope, scopedName.drop(1))
|
||||
} else {
|
||||
statementScope = (statementScope as Node).definingScope
|
||||
}
|
||||
}
|
||||
|
||||
// not found, try again but now assume it's a globally scoped name starting with the name of a block
|
||||
// a scoped name refers to a name in another namespace, and stars from the root.
|
||||
for(module in (this as Node).definingModule.program.modules) {
|
||||
module.statements.forEach {
|
||||
if(it is Block && it.name==scopedName[0])
|
||||
return it.lookup(scopedName)
|
||||
val block = module.searchSymbol(scopedName[0])
|
||||
if(block!=null) {
|
||||
var statement = block
|
||||
for(name in scopedName.drop(1)) {
|
||||
statement = (statement as? IStatementContainer)?.searchSymbol(name)
|
||||
if(statement==null)
|
||||
return null
|
||||
}
|
||||
return statement
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun resolveLocally(startScope: INameScope, name: List<String>): Statement? {
|
||||
var scope: INameScope? = startScope
|
||||
for(part in name.dropLast(1)) {
|
||||
scope = scope!!.subScope(part)
|
||||
if(scope==null)
|
||||
return null
|
||||
}
|
||||
return scope!!.searchSymbol(name.last())
|
||||
}
|
||||
|
||||
private fun lookupUnqualified(name: String): Statement? {
|
||||
val builtinFunctionsNames = (this as Node).definingModule.program.builtinFunctions.names
|
||||
if(name in builtinFunctionsNames) {
|
||||
@ -199,7 +177,6 @@ interface INameScope: IStatementContainer, INamedStatement {
|
||||
}
|
||||
|
||||
// search for the unqualified name in the current scope (and possibly in any anonymousscopes it may contain)
|
||||
// if it's not found there, jump up one higher in the namespaces and try again.
|
||||
var statementScope = this
|
||||
while(statementScope !is GlobalNamespace) {
|
||||
val symbol = statementScope.searchSymbol(name)
|
||||
@ -262,6 +239,7 @@ interface Node {
|
||||
}
|
||||
|
||||
fun replaceChildNode(node: Node, replacement: Node)
|
||||
fun copy(): Node
|
||||
}
|
||||
|
||||
|
||||
@ -277,8 +255,8 @@ open class Module(final override var statements: MutableList<Statement>,
|
||||
.substringAfterLast("/")
|
||||
.substringAfterLast("\\")
|
||||
|
||||
val loadAddress: Int by lazy {
|
||||
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
|
||||
val loadAddress: UInt by lazy {
|
||||
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0u
|
||||
address
|
||||
}
|
||||
|
||||
@ -288,11 +266,6 @@ open class Module(final override var statements: MutableList<Statement>,
|
||||
statements.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
fun linkIntoProgram(program: Program) {
|
||||
this.program = program
|
||||
linkParents(program.namespace)
|
||||
}
|
||||
|
||||
override val definingScope: INameScope
|
||||
get() = program.namespace
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
@ -302,6 +275,8 @@ open class Module(final override var statements: MutableList<Statement>,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy(): Node = throw NotImplementedError("no support for duplicating a Module")
|
||||
|
||||
override fun toString() = "Module(name=$name, pos=$position, lib=${isLibrary})"
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
@ -317,6 +292,8 @@ class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
|
||||
override val statements = mutableListOf<Statement>() // not used
|
||||
override var parent: Node = ParentSentinel
|
||||
|
||||
override fun copy(): Node = throw NotImplementedError("no support for duplicating a GlobalNamespace")
|
||||
|
||||
override fun lookup(scopedName: List<String>): Statement? {
|
||||
throw NotImplementedError("use lookup on actual ast node instead")
|
||||
}
|
||||
@ -330,12 +307,10 @@ class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
|
||||
}
|
||||
}
|
||||
|
||||
object BuiltinFunctionScopePlaceholder : INameScope {
|
||||
internal object BuiltinFunctionScopePlaceholder : INameScope {
|
||||
override val name = "<<builtin-functions-scope-placeholder>>"
|
||||
override val position = Position("<<placeholder>>", 0, 0, 0)
|
||||
override var statements = mutableListOf<Statement>()
|
||||
override var parent: Node = ParentSentinel
|
||||
override fun linkParents(parent: Node) {}
|
||||
}
|
||||
|
||||
|
||||
|
@ -16,4 +16,16 @@ fun Number.toHex(): String {
|
||||
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun UInt.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
return when (this) {
|
||||
in 0u until 16u -> this.toString()
|
||||
in 0u until 0x100u -> "$"+this.toString(16).padStart(2,'0')
|
||||
in 0u until 0x10000u -> "$"+this.toString(16).padStart(4,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ class Program(val name: String,
|
||||
require(null == _modules.firstOrNull { it.name == module.name })
|
||||
{ "module '${module.name}' already present" }
|
||||
_modules.add(module)
|
||||
module.linkIntoProgram(this)
|
||||
module.program = this
|
||||
module.linkParents(namespace)
|
||||
return this
|
||||
}
|
||||
|
||||
@ -71,10 +72,10 @@ class Program(val name: String,
|
||||
val toplevelModule: Module
|
||||
get() = modules.first { it.name!= internedStringsModuleName }
|
||||
|
||||
val definedLoadAddress: Int
|
||||
val definedLoadAddress: UInt
|
||||
get() = toplevelModule.loadAddress
|
||||
|
||||
var actualLoadAddress: Int = 0
|
||||
var actualLoadAddress = 0u
|
||||
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
|
||||
|
||||
fun internString(string: StringLiteralValue): List<String> {
|
||||
@ -93,7 +94,7 @@ class Program(val name: String,
|
||||
val varName = "string_${internedStringsBlock.statements.size}"
|
||||
val decl = VarDecl(
|
||||
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
|
||||
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, position = string.position
|
||||
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, subroutineParameter = null, position = string.position
|
||||
)
|
||||
internedStringsBlock.statements.add(decl)
|
||||
decl.linkParents(internedStringsBlock)
|
||||
|
@ -13,7 +13,7 @@ import kotlin.io.path.isRegularFile
|
||||
|
||||
/***************** Antlr Extension methods to create AST ****************/
|
||||
|
||||
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
||||
private data class NumericLiteral(val number: Double, val datatype: DataType)
|
||||
|
||||
|
||||
private fun ParserRuleContext.toPosition() : Position {
|
||||
@ -43,7 +43,7 @@ internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
|
||||
else -> throw FatalAstException("weird block node $it")
|
||||
}
|
||||
}
|
||||
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||
return Block(identifier().text, integerliteral()?.toAst()?.number?.toUInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||
@ -64,6 +64,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
vd.SHARED()!=null,
|
||||
null,
|
||||
it.toPosition()
|
||||
)
|
||||
}
|
||||
@ -81,6 +82,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
vd.SHARED() != null,
|
||||
null,
|
||||
cvarinit.toPosition()
|
||||
)
|
||||
}
|
||||
@ -98,6 +100,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
vd.SHARED()!=null,
|
||||
null,
|
||||
mvarinit.toPosition()
|
||||
)
|
||||
}
|
||||
@ -193,7 +196,7 @@ private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
|
||||
|
||||
private fun Prog8ANTLRParser.RomsubroutineContext.toAst(): Subroutine {
|
||||
val subdecl = asmsub_decl().toAst()
|
||||
val address = integerliteral().toAst().number.toInt()
|
||||
val address = integerliteral().toAst().number.toUInt()
|
||||
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes,
|
||||
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
|
||||
@ -293,7 +296,7 @@ private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
|
||||
val address = integerliteral()?.toAst()?.number?.toInt()
|
||||
val address = integerliteral()?.toAst()?.number?.toUInt()
|
||||
val identifier = scoped_identifier()?.toAst()
|
||||
return Jump(address, identifier, null, toPosition())
|
||||
}
|
||||
@ -354,7 +357,7 @@ private fun Prog8ANTLRParser.DirectiveargContext.toAst() : DirectiveArg {
|
||||
if(str?.ALT_STRING_ENCODING() != null)
|
||||
throw SyntaxError("can't use alternate string s for directive arguments", toPosition())
|
||||
|
||||
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
|
||||
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
@ -396,7 +399,7 @@ private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
}
|
||||
else -> throw FatalAstException("invalid radix")
|
||||
}
|
||||
return NumericLiteral(integer, datatype)
|
||||
return NumericLiteral(integer.toDouble(), datatype)
|
||||
}
|
||||
val terminal: TerminalNode = children[0] as TerminalNode
|
||||
val integerPart = this.intpart.text
|
||||
@ -420,11 +423,11 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
|
||||
val intLit = litval.integerliteral()?.toAst()
|
||||
when {
|
||||
intLit!=null -> when(intLit.datatype) {
|
||||
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number.toShort(), litval.toPosition())
|
||||
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number.toShort(), litval.toPosition())
|
||||
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number.toInt(), litval.toPosition())
|
||||
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number.toInt(), litval.toPosition())
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number.toDouble(), litval.toPosition())
|
||||
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number, litval.toPosition())
|
||||
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number, litval.toPosition())
|
||||
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number, litval.toPosition())
|
||||
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number, litval.toPosition())
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number, litval.toPosition())
|
||||
else -> throw FatalAstException("invalid datatype for numeric literal")
|
||||
}
|
||||
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
||||
@ -455,7 +458,7 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
|
||||
|
||||
if (rangefrom!=null && rangeto!=null) {
|
||||
val defaultstep = if(rto.text == "to") 1 else -1
|
||||
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
|
||||
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep.toDouble(), toPosition())
|
||||
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
||||
}
|
||||
|
||||
@ -600,6 +603,7 @@ private fun Prog8ANTLRParser.VardeclContext.toAst(): VarDecl {
|
||||
ARRAYSIG() != null || arrayindex() != null,
|
||||
false,
|
||||
SHARED()!=null,
|
||||
null,
|
||||
toPosition()
|
||||
)
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ enum class DataType {
|
||||
UWORD -> targetType.oneOf(UWORD, FLOAT)
|
||||
WORD -> targetType.oneOf(WORD, FLOAT)
|
||||
FLOAT -> targetType == FLOAT
|
||||
STR -> targetType == STR
|
||||
STR -> targetType.oneOf(STR, UWORD)
|
||||
in ArrayDatatypes -> targetType == this
|
||||
else -> false
|
||||
}
|
||||
@ -184,6 +184,8 @@ object ParentSentinel : Node {
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy(): Node = throw FatalAstException("should never duplicate a ParentSentinel")
|
||||
}
|
||||
|
||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||
|
@ -6,21 +6,23 @@ import prog8.ast.base.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
import java.util.*
|
||||
import kotlin.math.round
|
||||
|
||||
|
||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
val logicalOperators = setOf("and", "or", "xor", "not")
|
||||
val bitwiseOperators = setOf("&", "|", "^")
|
||||
|
||||
|
||||
sealed class Expression: Node {
|
||||
abstract override fun copy(): Expression
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
|
||||
abstract fun referencesIdentifier(nameInSource: List<String>): Boolean
|
||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||
abstract val isSimple: Boolean
|
||||
|
||||
@ -62,6 +64,18 @@ sealed class Expression: Node {
|
||||
else -> other==this
|
||||
}
|
||||
}
|
||||
|
||||
fun typecastTo(targetDt: DataType, sourceDt: DataType, implicit: Boolean=false): Pair<Boolean, Expression> {
|
||||
require(sourceDt!=DataType.UNDEFINED && targetDt!=DataType.UNDEFINED)
|
||||
if(sourceDt==targetDt)
|
||||
return Pair(false, this)
|
||||
if(this is TypecastExpression) {
|
||||
this.type = targetDt
|
||||
return Pair(false, this)
|
||||
}
|
||||
val typecast = TypecastExpression(this, targetDt, implicit, this.position)
|
||||
return Pair(true, typecast)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -79,11 +93,33 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun copy() = PrefixExpression(operator, expression.copy(), position)
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val constval = expression.constValue(program) ?: return null
|
||||
val converted = when(operator) {
|
||||
"+" -> constval
|
||||
"-" -> when (constval.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(-constval.number.toInt(), constval.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, -constval.number, constval.position)
|
||||
else -> throw ExpressionError("can only take negative of int or float", constval.position)
|
||||
}
|
||||
"~" -> when (constval.type) {
|
||||
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, constval.number.toInt().inv().toDouble(), constval.position)
|
||||
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, (constval.number.toInt().inv() and 255).toDouble(), constval.position)
|
||||
DataType.WORD -> NumericLiteralValue(DataType.WORD, constval.number.toInt().inv().toDouble(), constval.position)
|
||||
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
|
||||
}
|
||||
"not" -> NumericLiteralValue.fromBoolean(constval.number == 0.0, constval.position)
|
||||
else -> throw FatalAstException("invalid operator")
|
||||
}
|
||||
converted.linkParents(this.parent)
|
||||
return converted
|
||||
}
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = expression.referencesIdentifier(nameInSource)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val inferred = expression.inferType(program)
|
||||
return when(operator) {
|
||||
@ -106,7 +142,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
}
|
||||
}
|
||||
|
||||
override val isSimple = false
|
||||
override val isSimple = true
|
||||
|
||||
override fun toString(): String {
|
||||
return "Prefix($operator $expression)"
|
||||
@ -132,9 +168,8 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "[$left $operator $right]"
|
||||
}
|
||||
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position)
|
||||
override fun toString() = "[$left $operator $right]"
|
||||
|
||||
override val isSimple = false
|
||||
|
||||
@ -144,7 +179,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = left.referencesIdentifier(*scopedName) || right.referencesIdentifier(*scopedName)
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val leftDt = left.inferType(program)
|
||||
val rightDt = right.inferType(program)
|
||||
@ -158,7 +193,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
commonDatatype(
|
||||
leftDt.getOr(DataType.BYTE),
|
||||
rightDt.getOr(DataType.BYTE),
|
||||
null, null
|
||||
null, "", null
|
||||
).first
|
||||
)
|
||||
} catch (x: FatalAstException) {
|
||||
@ -180,7 +215,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
|
||||
companion object {
|
||||
fun commonDatatype(leftDt: DataType, rightDt: DataType,
|
||||
left: Expression?, right: Expression?): Pair<DataType, Expression?> {
|
||||
left: Expression?, operator: String, right: Expression?): Pair<DataType, Expression?> {
|
||||
// byte + byte -> byte
|
||||
// byte + word -> word
|
||||
// word + byte -> word
|
||||
@ -261,13 +296,13 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = arrayvar.referencesIdentifier(nameInSource)
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val target = arrayvar.targetStatement(program)
|
||||
if (target is VarDecl) {
|
||||
return when (target.datatype) {
|
||||
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||
DataType.STR, DataType.UWORD -> InferredTypes.knownFor(DataType.UBYTE)
|
||||
in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(target.datatype))
|
||||
else -> InferredTypes.unknown()
|
||||
}
|
||||
@ -279,7 +314,7 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
|
||||
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
|
||||
}
|
||||
|
||||
fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
|
||||
override fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
|
||||
}
|
||||
|
||||
class TypecastExpression(var expression: Expression, var type: DataType, val implicit: Boolean, override val position: Position) : Expression() {
|
||||
@ -298,16 +333,20 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = TypecastExpression(expression.copy(), type, implicit, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = expression.referencesIdentifier(nameInSource)
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(type)
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val cv = expression.constValue(program) ?: return null
|
||||
val cast = cv.cast(type)
|
||||
return if(cast.isValid)
|
||||
cast.valueOrZero()
|
||||
return if(cast.isValid) {
|
||||
val newval = cast.valueOrZero()
|
||||
newval.linkParents(parent)
|
||||
return newval
|
||||
}
|
||||
else
|
||||
null
|
||||
}
|
||||
@ -333,8 +372,9 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = AddressOf(identifier.copy(), position)
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = identifier.nameInSource==nameInSource
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UWORD)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@ -356,10 +396,11 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = DirectMemoryRead(addressExpression.copy(), position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = addressExpression.referencesIdentifier(nameInSource)
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UBYTE)
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
@ -369,43 +410,63 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
}
|
||||
|
||||
class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
val number: Number, // can be byte, word or float depending on the type
|
||||
numbervalue: Double, // can be byte, word or float depending on the type
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
val number: Double by lazy {
|
||||
if(type==DataType.FLOAT)
|
||||
numbervalue
|
||||
else {
|
||||
val rounded = round(numbervalue)
|
||||
if(rounded != numbervalue)
|
||||
throw ExpressionError("refused silent rounding of float to avoid loss of precision", position)
|
||||
rounded
|
||||
}
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
fun copy() = NumericLiteralValue(type, number, position)
|
||||
override fun copy() = NumericLiteralValue(type, number, position)
|
||||
|
||||
companion object {
|
||||
fun fromBoolean(bool: Boolean, position: Position) =
|
||||
NumericLiteralValue(DataType.UBYTE, if (bool) 1 else 0, position)
|
||||
NumericLiteralValue(DataType.UBYTE, if (bool) 1.0 else 0.0, position)
|
||||
|
||||
fun optimalNumeric(value: Number, position: Position): NumericLiteralValue {
|
||||
return if(value is Double) {
|
||||
NumericLiteralValue(DataType.FLOAT, value, position)
|
||||
} else {
|
||||
when (val intval = value.toInt()) {
|
||||
in 0..255 -> NumericLiteralValue(DataType.UBYTE, intval, position)
|
||||
in -128..127 -> NumericLiteralValue(DataType.BYTE, intval, position)
|
||||
in 0..65535 -> NumericLiteralValue(DataType.UWORD, intval, position)
|
||||
in -32768..32767 -> NumericLiteralValue(DataType.WORD, intval, position)
|
||||
else -> NumericLiteralValue(DataType.FLOAT, intval.toDouble(), position)
|
||||
val dvalue = value.toDouble()
|
||||
when (value.toInt()) {
|
||||
in 0..255 -> NumericLiteralValue(DataType.UBYTE, dvalue, position)
|
||||
in -128..127 -> NumericLiteralValue(DataType.BYTE, dvalue, position)
|
||||
in 0..65535 -> NumericLiteralValue(DataType.UWORD, dvalue, position)
|
||||
in -32768..32767 -> NumericLiteralValue(DataType.WORD, dvalue, position)
|
||||
else -> NumericLiteralValue(DataType.FLOAT, dvalue, position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun optimalInteger(value: Int, position: Position): NumericLiteralValue {
|
||||
val dvalue = value.toDouble()
|
||||
return when (value) {
|
||||
in 0..255 -> NumericLiteralValue(DataType.UBYTE, value, position)
|
||||
in -128..127 -> NumericLiteralValue(DataType.BYTE, value, position)
|
||||
in 0..65535 -> NumericLiteralValue(DataType.UWORD, value, position)
|
||||
in -32768..32767 -> NumericLiteralValue(DataType.WORD, value, position)
|
||||
else -> throw FatalAstException("integer overflow: $value")
|
||||
in 0..255 -> NumericLiteralValue(DataType.UBYTE, dvalue, position)
|
||||
in -128..127 -> NumericLiteralValue(DataType.BYTE, dvalue, position)
|
||||
in 0..65535 -> NumericLiteralValue(DataType.UWORD, dvalue, position)
|
||||
in -32768..32767 -> NumericLiteralValue(DataType.WORD, dvalue, position)
|
||||
else -> throw FatalAstException("integer overflow: $dvalue")
|
||||
}
|
||||
}
|
||||
|
||||
fun optimalInteger(value: UInt, position: Position): NumericLiteralValue {
|
||||
return when (value) {
|
||||
in 0u..255u -> NumericLiteralValue(DataType.UBYTE, value.toDouble(), position)
|
||||
in 0u..65535u -> NumericLiteralValue(DataType.UWORD, value.toDouble(), position)
|
||||
else -> throw FatalAstException("unsigned integer overflow: $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val asBooleanValue: Boolean = number.toDouble() != 0.0
|
||||
val asBooleanValue: Boolean = number != 0.0
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -415,7 +476,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = false
|
||||
override fun constValue(program: Program) = this
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
@ -430,13 +491,13 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is NumericLiteralValue)
|
||||
return false
|
||||
return number.toDouble()==other.number.toDouble()
|
||||
return number==other.number
|
||||
}
|
||||
|
||||
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
|
||||
operator fun compareTo(other: NumericLiteralValue): Int = number.compareTo(other.number)
|
||||
|
||||
class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
|
||||
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
|
||||
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
fun linkParent(parent: Node) {
|
||||
value?.linkParents(parent)
|
||||
}
|
||||
@ -451,55 +512,54 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
private fun internalCast(targettype: DataType): CastValue {
|
||||
if(type==targettype)
|
||||
return CastValue(true, this)
|
||||
val numval = number.toDouble()
|
||||
when(type) {
|
||||
DataType.UBYTE -> {
|
||||
if(targettype== DataType.BYTE && numval <= 127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.BYTE && number <= 127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.WORD || targettype== DataType.UWORD)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(targettype== DataType.UBYTE && numval >= 0)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UWORD && numval >= 0)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.UBYTE && number >= 0)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.UWORD && number >= 0)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.WORD)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(targettype== DataType.BYTE && numval <= 127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UBYTE && numval <= 255)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.WORD && numval <= 32767)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.BYTE && number <= 127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.UBYTE && number <= 255)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.WORD && number <= 32767)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UWORD && numval >=0)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.BYTE && number >= -128 && number <=127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.UBYTE && number >= 0 && number <= 255)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.UWORD && number >=0)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if (targettype == DataType.BYTE && number >= -128 && number <=127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if (targettype == DataType.UBYTE && number >=0 && number <= 255)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if (targettype == DataType.WORD && number >= -32768 && number <= 32767)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if (targettype == DataType.UWORD && number >=0 && number <= 65535)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@ -522,10 +582,11 @@ class CharLiteral(val value: Char,
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun copy() = CharLiteral(value, altEncoding, position)
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = false
|
||||
override fun constValue(program: Program): NumericLiteralValue {
|
||||
val bytevalue = program.encoding.encodeString(value.toString(), altEncoding).single()
|
||||
return NumericLiteralValue(DataType.UBYTE, bytevalue, position)
|
||||
return NumericLiteralValue(DataType.UBYTE, bytevalue.toDouble(), position)
|
||||
}
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
@ -551,13 +612,13 @@ class StringLiteralValue(val value: String,
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
fun copy() = StringLiteralValue(value, altEncoding, position)
|
||||
override fun copy() = StringLiteralValue(value, altEncoding, position)
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = false
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@ -583,6 +644,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
value.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a ArrayLiteralValue")
|
||||
override val isSimple = true
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
@ -592,7 +654,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = value.any { it.referencesIdentifier(*scopedName) }
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = value.any { it.referencesIdentifier(nameInSource) }
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@ -608,14 +670,6 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
return type==other.type && value.contentEquals(other.value)
|
||||
}
|
||||
|
||||
fun memsize(memsizer: IMemSizer): Int {
|
||||
if(type.isKnown) {
|
||||
val eltType = ArrayToElementTypes.getValue(type.getOr(DataType.UNDEFINED))
|
||||
return memsizer.memorySize(eltType) * value.size
|
||||
}
|
||||
else throw IllegalArgumentException("array datatype is not yet known")
|
||||
}
|
||||
|
||||
fun guessDatatype(program: Program): InferredTypes.InferredType {
|
||||
// Educated guess of the desired array literal's datatype.
|
||||
// If it's inside a for loop, assume the data type of the loop variable is what we want.
|
||||
@ -702,11 +756,12 @@ class RangeExpr(var from: Expression,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = RangeExpr(from.copy(), to.copy(), step.copy(), position)
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = from.referencesIdentifier(nameInSource) || to.referencesIdentifier(nameInSource)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val fromDt=from.inferType(program)
|
||||
val toDt=to.inferType(program)
|
||||
@ -759,6 +814,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun copy() = IdentifierReference(nameInSource, position)
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val node = definingScope.lookup(nameInSource) ?: throw UndefinedSymbolError(this)
|
||||
val vardecl = node as? VarDecl
|
||||
@ -777,8 +833,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String): Boolean =
|
||||
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = this.nameInSource==nameInSource
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
return when (val targetStmt = targetStatement(program)) {
|
||||
@ -809,6 +864,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
args.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun copy() = FunctionCall(target.copy(), args.map { it.copy() }.toMutableList(), position)
|
||||
override val isSimple = target.nameInSource.size==1 && (target.nameInSource[0] in arrayOf("msb", "lsb", "peek", "peekw"))
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
@ -846,7 +902,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String): Boolean = target.referencesIdentifier(*scopedName) || args.any{it.referencesIdentifier(*scopedName)}
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = target.referencesIdentifier(nameInSource) || args.any{it.referencesIdentifier(nameInSource)}
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val constVal = constValue(program ,false)
|
||||
|
@ -9,30 +9,26 @@ import prog8.ast.walk.IAstVisitor
|
||||
|
||||
interface INamedStatement {
|
||||
val name: String
|
||||
|
||||
val scopedName: List<String>
|
||||
get() {
|
||||
val scopedName = mutableListOf(name)
|
||||
var node: Node = this as Node
|
||||
while (node !is Block) {
|
||||
node = node.parent
|
||||
if(node is INameScope) {
|
||||
scopedName.add(0, node.name)
|
||||
}
|
||||
}
|
||||
return scopedName
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Statement : Node {
|
||||
abstract override fun copy(): Statement
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
|
||||
fun makeScopedName(name: String): String {
|
||||
// easy way out is to always return the full scoped name.
|
||||
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
|
||||
// and like this, we can cache the name even,
|
||||
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
|
||||
val scope = mutableListOf<String>()
|
||||
var statementScope = this.parent
|
||||
while(statementScope !is ParentSentinel && statementScope !is Module) {
|
||||
if(statementScope is INameScope) {
|
||||
scope.add(0, statementScope.name)
|
||||
}
|
||||
statementScope = statementScope.parent
|
||||
}
|
||||
if(name.isNotEmpty())
|
||||
scope.add(name)
|
||||
return scope.joinToString(".")
|
||||
}
|
||||
|
||||
fun nextSibling(): Statement? {
|
||||
val statements = (parent as? IStatementContainer)?.statements ?: return null
|
||||
val nextIdx = statements.indexOfFirst { it===this } + 1
|
||||
@ -62,17 +58,21 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a BuiltinFunctionStatementPlaceholder")
|
||||
}
|
||||
|
||||
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?)
|
||||
|
||||
class Block(override val name: String,
|
||||
val address: Int?,
|
||||
val address: UInt?,
|
||||
override var statements: MutableList<Statement>,
|
||||
val isInLibrary: Boolean,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a Block")
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
statements.forEach {it.linkParents(this)}
|
||||
@ -87,10 +87,7 @@ class Block(override val name: String,
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Block(name=$name, address=$address, ${statements.size} statements)"
|
||||
}
|
||||
override fun toString() = "Block(name=$name, address=$address, ${statements.size} statements)"
|
||||
|
||||
fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet()
|
||||
}
|
||||
@ -104,17 +101,19 @@ data class Directive(val directive: String, val args: List<DirectiveArg>, overri
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun copy() = Directive(directive, args.map { it.copy() }, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
|
||||
data class DirectiveArg(val str: String?, val name: String?, val int: UInt?, override val position: Position) : Node {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun copy() = DirectiveArg(str, name, int, position)
|
||||
}
|
||||
|
||||
data class Label(override val name: String, override val position: Position) : Statement(), INamedStatement {
|
||||
@ -125,12 +124,10 @@ data class Label(override val name: String, override val position: Position) : S
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun copy() = Label(name, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Label(name=$name, pos=$position)"
|
||||
}
|
||||
override fun toString()= "Label(name=$name, pos=$position)"
|
||||
}
|
||||
|
||||
open class Return(var value: Expression?, final override val position: Position) : Statement() {
|
||||
@ -147,12 +144,10 @@ open class Return(var value: Expression?, final override val position: Position)
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = Return(value?.copy(), position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Return($value, pos=$position)"
|
||||
}
|
||||
override fun toString() = "Return($value, pos=$position)"
|
||||
}
|
||||
|
||||
class Break(override val position: Position) : Statement() {
|
||||
@ -163,6 +158,7 @@ class Break(override val position: Position) : Statement() {
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun copy() = Break(position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -184,6 +180,7 @@ open class VarDecl(val type: VarDeclType,
|
||||
val isArray: Boolean,
|
||||
val autogeneratedDontRemove: Boolean,
|
||||
val sharedWithAsm: Boolean,
|
||||
val subroutineParameter: SubroutineParameter?,
|
||||
final override val position: Position) : Statement(), INamedStatement {
|
||||
override lateinit var parent: Node
|
||||
var allowInitializeWithZero = true
|
||||
@ -193,6 +190,16 @@ open class VarDecl(val type: VarDeclType,
|
||||
companion object {
|
||||
private var autoHeapValueSequenceNumber = 0
|
||||
|
||||
fun fromParameter(param: SubroutineParameter): VarDecl {
|
||||
return VarDecl(VarDeclType.VAR, param.type, ZeropageWish.DONTCARE, null, param.name, null,
|
||||
isArray = false,
|
||||
autogeneratedDontRemove = true,
|
||||
sharedWithAsm = false,
|
||||
subroutineParameter = param,
|
||||
position = param.position
|
||||
)
|
||||
}
|
||||
|
||||
fun createAuto(array: ArrayLiteralValue): VarDecl {
|
||||
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||
val arrayDt =
|
||||
@ -203,14 +210,14 @@ open class VarDecl(val type: VarDeclType,
|
||||
val declaredType = ArrayToElementTypes.getValue(arrayDt)
|
||||
val arraysize = ArrayIndex.forArray(array)
|
||||
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array,
|
||||
isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, position = array.position)
|
||||
isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, subroutineParameter = null, position = array.position)
|
||||
}
|
||||
|
||||
fun defaultZero(dt: DataType, position: Position) = when(dt) {
|
||||
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
|
||||
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
|
||||
DataType.UWORD, DataType.STR -> NumericLiteralValue(DataType.UWORD, 0, position)
|
||||
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
|
||||
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0.0, position)
|
||||
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0.0, position)
|
||||
DataType.UWORD, DataType.STR -> NumericLiteralValue(DataType.UWORD, 0.0, position)
|
||||
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0.0, position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
|
||||
else -> throw FatalAstException("can only determine default zero value for a numeric type")
|
||||
}
|
||||
@ -246,10 +253,8 @@ open class VarDecl(val type: VarDeclType,
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
|
||||
}
|
||||
override fun toString() =
|
||||
"VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
|
||||
|
||||
fun zeroElementValue(): NumericLiteralValue {
|
||||
if(allowInitializeWithZero)
|
||||
@ -258,17 +263,14 @@ open class VarDecl(val type: VarDeclType,
|
||||
throw IllegalArgumentException("attempt to get zero value for vardecl that shouldn't get it")
|
||||
}
|
||||
|
||||
fun copy(): VarDecl {
|
||||
val c = VarDecl(type, declaredDatatype, zeropage, arraysize, name, value, isArray, autogeneratedDontRemove, sharedWithAsm, position)
|
||||
override fun copy(): VarDecl {
|
||||
val c = VarDecl(type, declaredDatatype, zeropage, arraysize?.copy(), name, value?.copy(),
|
||||
isArray, autogeneratedDontRemove, sharedWithAsm, subroutineParameter, position)
|
||||
c.allowInitializeWithZero = this.allowInitializeWithZero
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
// a vardecl used only for subroutine parameters
|
||||
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
|
||||
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, false, true, false, position)
|
||||
|
||||
class ArrayIndex(var indexExpr: Expression,
|
||||
override val position: Position) : Node {
|
||||
override lateinit var parent: Node
|
||||
@ -292,16 +294,13 @@ class ArrayIndex(var indexExpr: Expression,
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) = indexExpr.accept(visitor)
|
||||
fun accept(visitor: AstWalker, parent: Node) = indexExpr.accept(visitor, this)
|
||||
|
||||
override fun toString(): String {
|
||||
return("ArrayIndex($indexExpr, pos=$position)")
|
||||
}
|
||||
fun accept(visitor: AstWalker) = indexExpr.accept(visitor, this)
|
||||
override fun toString() = "ArrayIndex($indexExpr, pos=$position)"
|
||||
|
||||
fun constIndex() = (indexExpr as? NumericLiteralValue)?.number?.toInt()
|
||||
|
||||
infix fun isSameAs(other: ArrayIndex): Boolean = indexExpr isSameAs other.indexExpr
|
||||
fun copy() = ArrayIndex(indexExpr, position)
|
||||
override fun copy() = ArrayIndex(indexExpr.copy(), position)
|
||||
}
|
||||
|
||||
open class Assignment(var target: AssignTarget, var value: Expression, final override val position: Position) : Statement() {
|
||||
@ -322,12 +321,10 @@ open class Assignment(var target: AssignTarget, var value: Expression, final ove
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy()= Assignment(target.copy(), value.copy(), position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return("Assignment(target: $target, value: $value, pos=$position)")
|
||||
}
|
||||
override fun toString() = "Assignment(target: $target, value: $value, pos=$position)"
|
||||
|
||||
/**
|
||||
* Is the assigment value an expression that references the assignment target itself?
|
||||
@ -340,20 +337,35 @@ open class Assignment(var target: AssignTarget, var value: Expression, final ove
|
||||
if(binExpr.left isSameAs target)
|
||||
return true // A = A <operator> Something
|
||||
|
||||
if(binExpr.operator in "+-") {
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if(rightBinExpr==null && leftBinExpr!=null && leftBinExpr.operator in "+-") {
|
||||
// A = (A +- x) +- y
|
||||
if(leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target)
|
||||
return true
|
||||
}
|
||||
if(leftBinExpr==null && rightBinExpr!=null && rightBinExpr.operator in "+-") {
|
||||
// A = y +- (A +- x)
|
||||
if(rightBinExpr.left isSameAs target || rightBinExpr.right isSameAs target || binExpr.left isSameAs target)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
if (binExpr.left !is BinaryExpression && binExpr.right isSameAs target)
|
||||
return true // A = v <associative-operator> A
|
||||
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
if(leftBinExpr?.operator == binExpr.operator) {
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if(leftBinExpr?.operator == binExpr.operator && rightBinExpr==null) {
|
||||
// one of these?
|
||||
// A = (A <associative-operator> x) <same-operator> y
|
||||
// A = (x <associative-operator> A) <same-operator> y
|
||||
// A = (x <associative-operator> y) <same-operator> A
|
||||
return leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target
|
||||
}
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if(rightBinExpr?.operator == binExpr.operator) {
|
||||
if(rightBinExpr?.operator == binExpr.operator && leftBinExpr==null) {
|
||||
// one of these?
|
||||
// A = y <associative-operator> (A <same-operator> x)
|
||||
// A = y <associative-operator> (x <same-operator> y)
|
||||
@ -423,7 +435,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
return when {
|
||||
identifier != null -> identifier!!.copy()
|
||||
arrayindexed != null -> arrayindexed!!.copy()
|
||||
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
|
||||
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression.copy(), memoryAddress.position)
|
||||
else -> throw FatalAstException("invalid assignmenttarget $this")
|
||||
}
|
||||
}
|
||||
@ -468,7 +480,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
return false
|
||||
}
|
||||
|
||||
fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position)
|
||||
override fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position)
|
||||
}
|
||||
|
||||
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
||||
@ -485,19 +497,18 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = PostIncrDecr(target.copy(), operator, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
|
||||
}
|
||||
override fun toString() = "PostIncrDecr(op: $operator, target: $target, pos=$position)"
|
||||
}
|
||||
|
||||
class Jump(val address: Int?,
|
||||
val identifier: IdentifierReference?,
|
||||
val generatedLabel: String?, // used in code generation scenarios
|
||||
override val position: Position) : Statement() {
|
||||
open class Jump(val address: UInt?,
|
||||
val identifier: IdentifierReference?,
|
||||
val generatedLabel: String?, // can be used in code generation scenarios
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
open val isGosub = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -505,12 +516,22 @@ class Jump(val address: Int?,
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun copy() = Jump(address, identifier?.copy(), generatedLabel, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
|
||||
}
|
||||
override fun toString() =
|
||||
"Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
|
||||
}
|
||||
|
||||
// a GoSub is ONLY created internally for calling subroutines
|
||||
class GoSub(address: UInt?, identifier: IdentifierReference?, generatedLabel: String?, position: Position) :
|
||||
Jump(address, identifier, generatedLabel, position) {
|
||||
|
||||
override val isGosub = true
|
||||
override fun copy() = GoSub(address, identifier?.copy(), generatedLabel, position)
|
||||
override fun toString() =
|
||||
"GoSub(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
|
||||
}
|
||||
|
||||
class FunctionCallStatement(override var target: IdentifierReference,
|
||||
@ -525,6 +546,8 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
args.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a FunctionCallStatement")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if(node===target)
|
||||
target = replacement as IdentifierReference
|
||||
@ -537,10 +560,7 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "FunctionCallStatement(target=$target, pos=$position)"
|
||||
}
|
||||
override fun toString() = "FunctionCallStatement(target=$target, pos=$position)"
|
||||
}
|
||||
|
||||
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
|
||||
@ -550,6 +570,9 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a InlineAssembly")
|
||||
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
@ -571,6 +594,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = AnonymousScope(statements.map { it.copy() }.toMutableList(), position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -583,6 +607,7 @@ class NopStatement(override val position: Position): Statement() {
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun copy() = NopStatement(position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -598,7 +623,7 @@ class AsmGenInfo {
|
||||
var usedFloatEvalResultVar1 = false
|
||||
var usedFloatEvalResultVar2 = false
|
||||
|
||||
val extraVars = mutableListOf<Triple<DataType, String, Int?>>()
|
||||
val extraVars = mutableListOf<Triple<DataType, String, UInt?>>()
|
||||
}
|
||||
|
||||
// the subroutine class covers both the normal user-defined subroutines,
|
||||
@ -610,7 +635,7 @@ class Subroutine(override val name: String,
|
||||
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<CpuRegister>,
|
||||
val asmAddress: Int?,
|
||||
val asmAddress: UInt?,
|
||||
val isAsmSubroutine: Boolean,
|
||||
val inline: Boolean,
|
||||
override var statements: MutableList<Statement>,
|
||||
@ -634,7 +659,8 @@ class Subroutine(override val name: String,
|
||||
|
||||
override lateinit var parent: Node
|
||||
val asmGenInfo = AsmGenInfo()
|
||||
val scopedname: String by lazy { makeScopedName(name) }
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a Subroutine")
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -660,10 +686,8 @@ class Subroutine(override val name: String,
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
|
||||
}
|
||||
override fun toString() =
|
||||
"Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
|
||||
|
||||
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
@ -687,6 +711,24 @@ class Subroutine(override val name: String,
|
||||
.filter { it is InlineAssembly }
|
||||
.map { (it as InlineAssembly).assembly }
|
||||
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it || " bra" in it || "\tbra" in it}
|
||||
|
||||
|
||||
// code to provide the ability to reference asmsub parameters via qualified name:
|
||||
private val asmParamsDecls = mutableMapOf<String, VarDecl>()
|
||||
|
||||
fun searchAsmParameter(name: String): VarDecl? {
|
||||
require(isAsmSubroutine)
|
||||
|
||||
val existingDecl = asmParamsDecls[name]
|
||||
if(existingDecl!=null)
|
||||
return existingDecl
|
||||
|
||||
val param = parameters.firstOrNull {it.name==name} ?: return null
|
||||
val decl = VarDecl.fromParameter(param)
|
||||
decl.linkParents(this)
|
||||
asmParamsDecls[name] = decl
|
||||
return decl
|
||||
}
|
||||
}
|
||||
|
||||
open class SubroutineParameter(val name: String,
|
||||
@ -701,6 +743,9 @@ open class SubroutineParameter(val name: String,
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace anything in a subroutineparameter node")
|
||||
}
|
||||
|
||||
override fun copy() = SubroutineParameter(name, type, position)
|
||||
override fun toString() = "Param($type:$name)"
|
||||
}
|
||||
|
||||
class IfStatement(var condition: Expression,
|
||||
@ -716,6 +761,8 @@ class IfStatement(var condition: Expression,
|
||||
elsepart.linkParents(this)
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a IfStatement")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===condition -> condition = replacement as Expression
|
||||
@ -743,6 +790,8 @@ class BranchStatement(var condition: BranchCondition,
|
||||
elsepart.linkParents(this)
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a BranchStatement")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===truepart -> truepart = replacement as AnonymousScope
|
||||
@ -770,6 +819,8 @@ class ForLoop(var loopVar: IdentifierReference,
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a ForLoop")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===loopVar -> loopVar = replacement as IdentifierReference
|
||||
@ -782,10 +833,7 @@ class ForLoop(var loopVar: IdentifierReference,
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
|
||||
}
|
||||
override fun toString() = "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
|
||||
|
||||
fun loopVarDt(program: Program) = loopVar.inferType(program)
|
||||
}
|
||||
@ -801,6 +849,8 @@ class WhileLoop(var condition: Expression,
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a WhileLoop")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===condition -> condition = replacement as Expression
|
||||
@ -823,6 +873,8 @@ class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a RepeatLoop")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===iterations -> iterations = replacement as Expression
|
||||
@ -846,6 +898,7 @@ class UntilLoop(var body: AnonymousScope,
|
||||
condition.linkParents(this)
|
||||
body.linkParents(this)
|
||||
}
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a UntilLoop")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
@ -871,6 +924,8 @@ class WhenStatement(var condition: Expression,
|
||||
choices.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun copy() = throw NotImplementedError("no support for duplicating a WhenStatement")
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if(node===condition)
|
||||
condition = replacement as Expression
|
||||
@ -927,9 +982,8 @@ class WhenChoice(var values: MutableList<Expression>?, // if null, th
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Choice($values at $position)"
|
||||
}
|
||||
override fun copy() = WhenChoice(values?.map{ it.copy() }?.toMutableList(), statements.copy(), position)
|
||||
override fun toString() = "Choice($values at $position)"
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
@ -949,11 +1003,8 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "DirectMemoryWrite($addressExpression)"
|
||||
}
|
||||
|
||||
override fun toString() = "DirectMemoryWrite($addressExpression)"
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
fun copy() = DirectMemoryWrite(addressExpression, position)
|
||||
override fun copy() = DirectMemoryWrite(addressExpression.copy(), position)
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ abstract class AstWalker {
|
||||
fun visit(decl: VarDecl, parent: Node) {
|
||||
track(before(decl, parent), decl, parent)
|
||||
decl.value?.accept(this, decl)
|
||||
decl.arraysize?.accept(this, decl)
|
||||
decl.arraysize?.accept(this)
|
||||
track(after(decl, parent), decl, parent)
|
||||
}
|
||||
|
||||
@ -375,7 +375,7 @@ abstract class AstWalker {
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
|
||||
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.indexer.accept(this)
|
||||
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package prog8.compilerinterface
|
||||
|
||||
interface IStringEncoding {
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<UByte>
|
||||
fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ sealed class SourceCode {
|
||||
val inpStr = object {}.javaClass.getResourceAsStream(normalized)!!
|
||||
// CharStreams.fromStream() doesn't allow us to set the stream name properly, so we use a lower level api
|
||||
val channel = Channels.newChannel(inpStr)
|
||||
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1);
|
||||
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1)
|
||||
}
|
||||
|
||||
override fun readText(): String {
|
||||
|
8
compilerAst/test/ProjectConfig.kt
Normal file
8
compilerAst/test/ProjectConfig.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import kotlin.math.max
|
||||
|
||||
object ProjectConfig : AbstractProjectConfig() {
|
||||
override val parallelism = max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.core.spec.style.AnnotationSpec
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.AstToSourceTextConverter
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
@ -12,11 +13,9 @@ import prog8.parser.SourceCode
|
||||
import prog8tests.ast.helpers.DummyFunctions
|
||||
import prog8tests.ast.helpers.DummyMemsizer
|
||||
import prog8tests.ast.helpers.DummyStringEncoder
|
||||
import kotlin.test.assertContains
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAstToSourceText {
|
||||
class TestAstToSourceText: AnnotationSpec() {
|
||||
|
||||
private fun generateP8(module: Module) : String {
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
@ -35,8 +34,7 @@ class TestAstToSourceText {
|
||||
val parsedAgain = parseModule(SourceCode.Text(generatedText))
|
||||
return Pair(generatedText, parsedAgain)
|
||||
} catch (e: ParseError) {
|
||||
assert(false) { "should produce valid Prog8 but threw $e" }
|
||||
throw e
|
||||
fail("should produce valid Prog8 but threw $e")
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,24 +42,21 @@ class TestAstToSourceText {
|
||||
fun testMentionsInternedStringsModule() {
|
||||
val orig = SourceCode.Text("\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex(";.*$internedStringsModuleName"))
|
||||
txt shouldContain Regex(";.*$internedStringsModuleName")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportDirectiveWithLib() {
|
||||
val orig = SourceCode.Text("%import textio\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("%import +textio"))
|
||||
txt shouldContain Regex("%import +textio")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportDirectiveWithUserModule() {
|
||||
val orig = SourceCode.Text("%import my_own_stuff\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("%import +my_own_stuff"))
|
||||
txt shouldContain Regex("%import +my_own_stuff")
|
||||
}
|
||||
|
||||
|
||||
@ -73,8 +68,7 @@ class TestAstToSourceText {
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("str +s += +\"fooBar\\\\n\""))
|
||||
txt shouldContain Regex("str +s += +\"fooBar\\\\n\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -85,8 +79,7 @@ class TestAstToSourceText {
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("str +sAlt += +@\"fooBar\\\\n\""))
|
||||
txt shouldContain Regex("str +sAlt += +@\"fooBar\\\\n\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -97,8 +90,7 @@ class TestAstToSourceText {
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("ubyte +c += +'x'"), "char literal")
|
||||
txt shouldContain Regex("ubyte +c += +'x'")
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -109,8 +101,7 @@ class TestAstToSourceText {
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("ubyte +cAlt += +@'x'"), "alt char literal")
|
||||
txt shouldContain Regex("ubyte +cAlt += +@'x'")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.or
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.string.shouldStartWith
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.CharLiteral
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
@ -24,39 +28,30 @@ import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestProg8Parser {
|
||||
class TestProg8Parser: FunSpec( {
|
||||
|
||||
@Nested
|
||||
inner class Newline {
|
||||
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)
|
||||
|
||||
@Nested
|
||||
inner class AtEnd {
|
||||
|
||||
@Test
|
||||
fun `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)
|
||||
|
||||
// #40: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
|
||||
val module = parseModule(src)
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `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 module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(1, module.statements.size)
|
||||
}
|
||||
// #40: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
|
||||
val module = parseModule(src)
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is required after each block except the last`() {
|
||||
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 module = parseModule(SourceCode.Text(srcText))
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
context("Newline") {
|
||||
test("is required after each block except the last") {
|
||||
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
|
||||
@ -65,22 +60,21 @@ class TestProg8Parser {
|
||||
// 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 + "}"
|
||||
|
||||
assertFailsWith<ParseError> { parseModule(SourceCode.Text(srcBad)) }
|
||||
shouldThrow<ParseError> { parseModule(SourceCode.Text(srcBad)) }
|
||||
val module = parseModule(SourceCode.Text(srcGood))
|
||||
assertEquals(2, module.statements.size)
|
||||
module.statements.size shouldBe 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is required between two Blocks or Directives - #47`() {
|
||||
test("is required between two Blocks or Directives - #47") {
|
||||
// block and block
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
|
||||
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
|
||||
blockA {
|
||||
} blockB {
|
||||
}
|
||||
""")) }
|
||||
|
||||
// block and directive
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
|
||||
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
|
||||
blockB {
|
||||
} %import textio
|
||||
""")) }
|
||||
@ -89,18 +83,17 @@ class TestProg8Parser {
|
||||
// Leaving them in anyways.
|
||||
|
||||
// dir and block
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
|
||||
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
|
||||
%import textio blockB {
|
||||
}
|
||||
""")) }
|
||||
|
||||
assertFailsWith<ParseError>{ parseModule(SourceCode.Text("""
|
||||
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
|
||||
%import textio %import syslib
|
||||
""")) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can be Win, Unix or mixed, even mixed`() {
|
||||
test("can be Win, Unix or mixed, even mixed") {
|
||||
val nlWin = "\r\n"
|
||||
val nlUnix = "\n"
|
||||
val nlMac = "\r"
|
||||
@ -124,15 +117,13 @@ class TestProg8Parser {
|
||||
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
|
||||
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(2, module.statements.size)
|
||||
module.statements.size shouldBe 2
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class EOLsInterleavedWithComments {
|
||||
context("EOLsInterleavedWithComments") {
|
||||
|
||||
@Test
|
||||
fun `are ok before first block - #47`() {
|
||||
test("are ok before first block - #47") {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
; comment
|
||||
@ -143,11 +134,10 @@ class TestProg8Parser {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(1, module.statements.size)
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `are ok between blocks - #47`() {
|
||||
test("are ok between blocks - #47") {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
blockA {
|
||||
@ -160,11 +150,10 @@ class TestProg8Parser {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(2, module.statements.size)
|
||||
module.statements.size shouldBe 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `are ok after last block - #47`() {
|
||||
test("are ok after last block - #47") {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
blockA {
|
||||
@ -175,45 +164,36 @@ class TestProg8Parser {
|
||||
|
||||
"""
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertEquals(1, module.statements.size)
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
inner class ImportDirectives {
|
||||
@Test
|
||||
fun `should not be looked into by the parser`() {
|
||||
context("ImportDirectives") {
|
||||
test("should not be looked into by the parser") {
|
||||
val importedNoExt = assumeNotExists(fixturesDir, "i_do_not_exist")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.p8")
|
||||
val text = "%import ${importedNoExt.name}"
|
||||
val module = parseModule(SourceCode.Text(text))
|
||||
|
||||
assertEquals(1, module.statements.size)
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
inner class EmptySourcecode {
|
||||
@Test
|
||||
fun `from an empty string should result in empty Module`() {
|
||||
context("EmptySourcecode") {
|
||||
test("from an empty string should result in empty Module") {
|
||||
val module = parseModule(SourceCode.Text(""))
|
||||
assertEquals(0, module.statements.size)
|
||||
module.statements.size shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `from an empty file should result in empty Module`() {
|
||||
test("from an empty file should result in empty Module") {
|
||||
val path = assumeReadableFile(fixturesDir, "empty.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertEquals(0, module.statements.size)
|
||||
module.statements.size shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class NameOfModule {
|
||||
@Test
|
||||
fun `parsed from a string`() {
|
||||
context("NameOfModule") {
|
||||
test("parsed from a string") {
|
||||
val srcText = """
|
||||
main {
|
||||
}
|
||||
@ -221,21 +201,19 @@ class TestProg8Parser {
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
|
||||
// Note: assertContains has *actual* as first param
|
||||
assertContains(module.name, Regex("^<String@[0-9a-f\\-]+>$"))
|
||||
module.name shouldContain Regex("^<String@[0-9a-f\\-]+>$")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parsed from a file`() {
|
||||
test("parsed from a file") {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertEquals(path.nameWithoutExtension, module.name)
|
||||
module.name shouldBe path.nameWithoutExtension
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class PositionOfAstNodesAndParseErrors {
|
||||
context("PositionOfAstNodesAndParseErrors") {
|
||||
|
||||
private fun assertPosition(
|
||||
fun assertPosition(
|
||||
actual: Position,
|
||||
expFile: String? = null,
|
||||
expLine: Int? = null,
|
||||
@ -243,13 +221,13 @@ class TestProg8Parser {
|
||||
expEndCol: Int? = null
|
||||
) {
|
||||
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
|
||||
if (expLine != null) assertEquals(expLine, actual.line, ".position.line (1-based)")
|
||||
if (expStartCol != null) assertEquals(expStartCol, actual.startCol, ".position.startCol (0-based)")
|
||||
if (expEndCol != null) assertEquals(expEndCol, actual.endCol, ".position.endCol (0-based)")
|
||||
if (expFile != null) assertEquals(expFile, actual.file, ".position.file")
|
||||
if (expLine != null) actual.line shouldBe expLine
|
||||
if (expStartCol != null) actual.startCol shouldBe expStartCol
|
||||
if (expEndCol != null) actual.endCol shouldBe expEndCol
|
||||
if (expFile != null) actual.file shouldBe expFile
|
||||
}
|
||||
|
||||
private fun assertPosition(
|
||||
fun assertPosition(
|
||||
actual: Position,
|
||||
expFile: Regex? = null,
|
||||
expLine: Int? = null,
|
||||
@ -257,14 +235,13 @@ class TestProg8Parser {
|
||||
expEndCol: Int? = null
|
||||
) {
|
||||
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
|
||||
if (expLine != null) assertEquals(expLine, actual.line, ".position.line (1-based)")
|
||||
if (expStartCol != null) assertEquals(expStartCol, actual.startCol, ".position.startCol (0-based)")
|
||||
if (expEndCol != null) assertEquals(expEndCol, actual.endCol, ".position.endCol (0-based)")
|
||||
// Note: assertContains expects *actual* value first
|
||||
if (expFile != null) assertContains(actual.file, expFile, ".position.file")
|
||||
if (expLine != null) actual.line shouldBe expLine
|
||||
if (expStartCol != null) actual.startCol shouldBe expStartCol
|
||||
if (expEndCol != null) actual.endCol shouldBe expEndCol
|
||||
if (expFile != null) actual.file shouldContain expFile
|
||||
}
|
||||
|
||||
private fun assertPositionOf(
|
||||
fun assertPositionOf(
|
||||
actual: Node,
|
||||
expFile: String? = null,
|
||||
expLine: Int? = null,
|
||||
@ -273,7 +250,7 @@ class TestProg8Parser {
|
||||
) =
|
||||
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
|
||||
|
||||
private fun assertPositionOf(
|
||||
fun assertPositionOf(
|
||||
actual: Node,
|
||||
expFile: Regex? = null,
|
||||
expLine: Int? = null,
|
||||
@ -283,24 +260,21 @@ class TestProg8Parser {
|
||||
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
|
||||
|
||||
|
||||
@Test
|
||||
fun `in ParseError from bad string source code`() {
|
||||
test("in ParseError from bad string source code") {
|
||||
val srcText = "bad * { }\n"
|
||||
|
||||
val e = assertFailsWith<ParseError> { parseModule(SourceCode.Text(srcText)) }
|
||||
val e = shouldThrow<ParseError> { parseModule(SourceCode.Text(srcText)) }
|
||||
assertPosition(e.position, Regex("^<String@[0-9a-f\\-]+>$"), 1, 4, 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ParseError from bad file source code`() {
|
||||
test("in ParseError from bad file source code") {
|
||||
val path = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val e = assertFailsWith<ParseError> { parseModule(SourceCode.File(path)) }
|
||||
val e = shouldThrow<ParseError> { parseModule(SourceCode.File(path)) }
|
||||
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `of Module parsed from a string`() {
|
||||
test("of Module parsed from a string") {
|
||||
val srcText = """
|
||||
main {
|
||||
}
|
||||
@ -309,15 +283,13 @@ class TestProg8Parser {
|
||||
assertPositionOf(module, Regex("^<String@[0-9a-f\\-]+>$"), 1, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `of Module parsed from a file`() {
|
||||
test("of Module parsed from a file") {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `of non-root Nodes parsed from file`() {
|
||||
test("of non-root Nodes parsed from file") {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
@ -329,9 +301,7 @@ class TestProg8Parser {
|
||||
assertPositionOf(startSub, mpf, 3, 4, 6)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `of non-root Nodes parsed from a string`() {
|
||||
test("of non-root Nodes parsed from a string") {
|
||||
val srcText = """
|
||||
%zeropage basicsafe
|
||||
main {
|
||||
@ -369,20 +339,30 @@ class TestProg8Parser {
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class PositionFile {
|
||||
@Test
|
||||
fun `isn't absolute for filesystem paths`() {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertSomethingForAllNodes(module) {
|
||||
assertFalse(Path(it.position.file).isAbsolute)
|
||||
assertTrue(Path(it.position.file).isRegularFile())
|
||||
context("PositionFile") {
|
||||
fun assertSomethingForAllNodes(module: Module, asserter: (Node) -> Unit) {
|
||||
asserter(module)
|
||||
module.statements.forEach(asserter)
|
||||
module.statements.filterIsInstance<Block>().forEach { b ->
|
||||
asserter(b)
|
||||
b.statements.forEach(asserter)
|
||||
b.statements.filterIsInstance<Subroutine>().forEach { s ->
|
||||
asserter(s)
|
||||
s.statements.forEach(asserter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is mangled string id for string sources`()
|
||||
test("isn't absolute for filesystem paths") {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertSomethingForAllNodes(module) {
|
||||
Path(it.position.file).isAbsolute shouldBe false
|
||||
Path(it.position.file).isRegularFile() shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("is mangled string id for string sources")
|
||||
{
|
||||
val srcText="""
|
||||
%zeropage basicsafe
|
||||
@ -395,38 +375,23 @@ class TestProg8Parser {
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertSomethingForAllNodes(module) {
|
||||
assertTrue(it.position.file.startsWith(SourceCode.stringSourcePrefix))
|
||||
it.position.file shouldStartWith SourceCode.stringSourcePrefix
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is library prefixed path for resources`()
|
||||
test("is library prefixed path for resources")
|
||||
{
|
||||
val resource = SourceCode.Resource("prog8lib/math.p8")
|
||||
val module = parseModule(resource)
|
||||
assertSomethingForAllNodes(module) {
|
||||
assertTrue(it.position.file.startsWith(SourceCode.libraryFilePrefix))
|
||||
it.position.file shouldStartWith SourceCode.libraryFilePrefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertSomethingForAllNodes(module: Module, asserter: (Node) -> Unit) {
|
||||
asserter(module)
|
||||
module.statements.forEach(asserter)
|
||||
module.statements.filterIsInstance<Block>().forEach { b ->
|
||||
asserter(b)
|
||||
b.statements.forEach(asserter)
|
||||
b.statements.filterIsInstance<Subroutine>().forEach { s ->
|
||||
asserter(s)
|
||||
s.statements.forEach(asserter)
|
||||
}
|
||||
}
|
||||
} }
|
||||
context("CharLiterals") {
|
||||
|
||||
@Nested
|
||||
inner class CharLiterals {
|
||||
|
||||
@Test
|
||||
fun `in argument position, no altEnc`() {
|
||||
test("in argument position, no altEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
@ -441,13 +406,12 @@ class TestProg8Parser {
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>().first()
|
||||
|
||||
assertIs<CharLiteral>(funCall.args[0])
|
||||
funCall.args[0] shouldBe(instanceOf<CharLiteral>())
|
||||
val char = funCall.args[0] as CharLiteral
|
||||
assertEquals('\n', char.value)
|
||||
char.value shouldBe '\n'
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on rhs of block-level var decl, no AltEnc`() {
|
||||
test("on rhs of block-level var decl, no AltEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
ubyte c = 'x'
|
||||
@ -459,12 +423,11 @@ class TestProg8Parser {
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
assertEquals('x', rhs.value, "char literal's .value")
|
||||
assertEquals(false, rhs.altEncoding, "char literal's .altEncoding")
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on rhs of block-level const decl, with AltEnc`() {
|
||||
test("on rhs of block-level const decl, with AltEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
const ubyte c = @'x'
|
||||
@ -476,12 +439,11 @@ class TestProg8Parser {
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
assertEquals('x', rhs.value, "char literal's .value")
|
||||
assertEquals(true, rhs.altEncoding, "char literal's .altEncoding")
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on rhs of subroutine-level var decl, no AltEnc`() {
|
||||
test("on rhs of subroutine-level var decl, no AltEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
@ -496,12 +458,11 @@ class TestProg8Parser {
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
assertEquals('x', rhs.value, "char literal's .value")
|
||||
assertEquals(false, rhs.altEncoding, "char literal's .altEncoding")
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on rhs of subroutine-level const decl, with AltEnc`() {
|
||||
test("on rhs of subroutine-level const decl, with AltEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
@ -516,16 +477,14 @@ class TestProg8Parser {
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
assertEquals('x', rhs.value, "char literal's .value")
|
||||
assertEquals(true, rhs.altEncoding, "char literal's .altEncoding")
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Ranges {
|
||||
context("Ranges") {
|
||||
|
||||
@Test
|
||||
fun `in for-loops`() {
|
||||
test("in for-loops") {
|
||||
val module = parseModule(SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
@ -549,67 +508,64 @@ class TestProg8Parser {
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
|
||||
assertEquals(5, iterables.size)
|
||||
iterables.size shouldBe 5
|
||||
|
||||
val it0 = iterables[0] as RangeExpr
|
||||
assertIs<StringLiteralValue>(it0.from, "parser should leave it as is")
|
||||
assertIs<StringLiteralValue>(it0.to, "parser should leave it as is")
|
||||
it0.from shouldBe instanceOf<StringLiteralValue>()
|
||||
it0.to shouldBe instanceOf<StringLiteralValue>()
|
||||
|
||||
val it1 = iterables[1] as StringLiteralValue
|
||||
assertEquals("something", it1.value, "parser should leave it as is")
|
||||
it1.value shouldBe "something"
|
||||
|
||||
val it2 = iterables[2] as RangeExpr
|
||||
assertIs<CharLiteral>(it2.from, "parser should leave it as is")
|
||||
assertIs<CharLiteral>(it2.to, "parser should leave it as is")
|
||||
it2.from shouldBe instanceOf<CharLiteral>()
|
||||
it2.to shouldBe instanceOf<CharLiteral>()
|
||||
|
||||
val it3 = iterables[3] as RangeExpr
|
||||
assertIs<NumericLiteralValue>(it3.from, "parser should leave it as is")
|
||||
assertIs<NumericLiteralValue>(it3.to, "parser should leave it as is")
|
||||
it3.from shouldBe instanceOf<NumericLiteralValue>()
|
||||
it3.to shouldBe instanceOf<NumericLiteralValue>()
|
||||
|
||||
val it4 = iterables[4] as RangeExpr
|
||||
assertIs<NumericLiteralValue>(it4.from, "parser should leave it as is")
|
||||
assertIs<NumericLiteralValue>(it4.to, "parser should leave it as is")
|
||||
it4.from shouldBe instanceOf<NumericLiteralValue>()
|
||||
it4.to shouldBe instanceOf<NumericLiteralValue>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharLiteralConstValue() {
|
||||
test("testCharLiteralConstValue") {
|
||||
val char1 = CharLiteral('A', false, Position.DUMMY)
|
||||
val char2 = CharLiteral('z', true, Position.DUMMY)
|
||||
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, AsciiStringEncoder)
|
||||
assertEquals(65, char1.constValue(program).number.toInt())
|
||||
assertEquals(122, char2.constValue(program).number.toInt())
|
||||
char1.constValue(program).number.toInt() shouldBe 65
|
||||
char2.constValue(program).number.toInt() shouldBe 122
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLiteralValueComparisons() {
|
||||
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
|
||||
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
|
||||
assertEquals(ten, ten)
|
||||
assertNotEquals(ten, nine)
|
||||
assertFalse(ten != ten)
|
||||
assertTrue(ten != nine)
|
||||
test("testLiteralValueComparisons") {
|
||||
val ten = NumericLiteralValue(DataType.UWORD, 10.0, Position.DUMMY)
|
||||
val nine = NumericLiteralValue(DataType.UBYTE, 9.0, Position.DUMMY)
|
||||
ten shouldBe ten
|
||||
nine shouldNotBe ten
|
||||
(ten != ten) shouldBe false
|
||||
(ten != nine) shouldBe true
|
||||
|
||||
assertTrue(ten > nine)
|
||||
assertTrue(ten >= nine)
|
||||
assertTrue(ten >= ten)
|
||||
assertFalse(ten > ten)
|
||||
(ten > nine) shouldBe true
|
||||
(ten >= nine) shouldBe true
|
||||
(ten >= ten) shouldBe true
|
||||
(ten > ten) shouldBe false
|
||||
|
||||
assertFalse(ten < nine)
|
||||
assertFalse(ten <= nine)
|
||||
assertTrue(ten <= ten)
|
||||
assertFalse(ten < ten)
|
||||
(ten < nine) shouldBe false
|
||||
(ten <= nine) shouldBe false
|
||||
(ten <= ten) shouldBe true
|
||||
(ten < ten) shouldBe false
|
||||
|
||||
val abc = StringLiteralValue("abc", false, Position.DUMMY)
|
||||
val abd = StringLiteralValue("abd", false, Position.DUMMY)
|
||||
assertEquals(abc, abc)
|
||||
assertTrue(abc!=abd)
|
||||
assertFalse(abc!=abc)
|
||||
abc shouldBe abc
|
||||
(abc!=abd) shouldBe true
|
||||
(abc!=abc) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnonScopeStillContainsVarsDirectlyAfterParse() {
|
||||
test("testAnonScopeStillContainsVarsDirectlyAfterParse") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
@ -624,17 +580,22 @@ class TestProg8Parser {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val start = mainBlock.statements.single() as Subroutine
|
||||
val repeatbody = (start.statements.single() as RepeatLoop).body
|
||||
assertFalse(mainBlock.statements.any { it is VarDecl }, "no vars moved to main block")
|
||||
assertFalse(start.statements.any { it is VarDecl }, "no vars moved to start sub")
|
||||
assertTrue(repeatbody.statements[0] is VarDecl, "var is still in repeat block (anonymousscope)")
|
||||
withClue("no vars moved to main block") {
|
||||
mainBlock.statements.any { it is VarDecl } shouldBe false
|
||||
}
|
||||
withClue("no vars moved to start sub") {
|
||||
start.statements.any { it is VarDecl } shouldBe false
|
||||
}
|
||||
withClue("\"var is still in repeat block (anonymousscope") {
|
||||
repeatbody.statements[0] shouldBe instanceOf<VarDecl>()
|
||||
}
|
||||
val initvalue = (repeatbody.statements[0] as VarDecl).value as? NumericLiteralValue
|
||||
assertEquals(99, initvalue?.number?.toInt())
|
||||
assertTrue(repeatbody.statements[1] is PostIncrDecr)
|
||||
initvalue?.number?.toInt() shouldBe 99
|
||||
repeatbody.statements[1] shouldBe instanceOf<PostIncrDecr>()
|
||||
// the ast processing steps used in the compiler, will eventually move the var up to the containing scope (subroutine).
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLabelsWithAnonScopesParsesFine() {
|
||||
test("testLabelsWithAnonScopesParsesFine") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
@ -661,6 +622,151 @@ class TestProg8Parser {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val start = mainBlock.statements.single() as Subroutine
|
||||
val labels = start.statements.filterIsInstance<Label>()
|
||||
assertEquals(1, labels.size, "only one label in subroutine scope")
|
||||
labels.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
test("logical operator 'not' priority") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte xx
|
||||
xx = not 4 and not 5
|
||||
xx = not 4 or not 5
|
||||
xx = not 4 xor not 5
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val start = (module.statements.single() as Block).statements.single() as Subroutine
|
||||
val andAssignmentExpr = (start.statements[1] as Assignment).value
|
||||
val orAssignmentExpr = (start.statements[2] as Assignment).value
|
||||
val xorAssignmentExpr = (start.statements[3] as Assignment).value
|
||||
|
||||
fun correctPrios(expr: Expression, operator: String) {
|
||||
withClue("not should have higher prio as the other logical operators") {
|
||||
expr shouldBe instanceOf<BinaryExpression>()
|
||||
val binExpr = expr as BinaryExpression
|
||||
binExpr.operator shouldBe operator
|
||||
(binExpr.left as PrefixExpression).operator shouldBe "not"
|
||||
(binExpr.right as PrefixExpression).operator shouldBe "not"
|
||||
}
|
||||
}
|
||||
|
||||
correctPrios(andAssignmentExpr, "and")
|
||||
correctPrios(orAssignmentExpr, "or")
|
||||
correctPrios(xorAssignmentExpr, "xor")
|
||||
}
|
||||
|
||||
test("inferred type correct for binaryexpression") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
ubyte bb
|
||||
uword ww
|
||||
ubyte bb2 = not bb or not ww ; expression combining ubyte and uword
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val bb2 = (module.statements.single() as Block).statements[2] as VarDecl
|
||||
val expr = bb2.value as BinaryExpression
|
||||
println(expr)
|
||||
expr.operator shouldBe "or"
|
||||
expr.left.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
expr.right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
expr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
test("inferred type for typecasted expressions with logical operators") {
|
||||
val src=SourceCode.Text("""
|
||||
main {
|
||||
ubyte bb
|
||||
uword ww
|
||||
uword qq = (not bb as uword)
|
||||
uword zz = not bb or not ww
|
||||
ubyte bb2 = not bb or not ww
|
||||
uword zz2 = (not bb as uword) or not ww
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val stmts = (module.statements.single() as Block).statements
|
||||
stmts.size shouldBe 6
|
||||
val qq = (stmts[2] as VarDecl).value as TypecastExpression
|
||||
val zz = (stmts[3] as VarDecl).value as BinaryExpression
|
||||
val bb2 = (stmts[4] as VarDecl).value as BinaryExpression
|
||||
val zz2 = (stmts[5] as VarDecl).value as BinaryExpression
|
||||
qq.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
zz.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
bb2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
zz2.operator shouldBe "or"
|
||||
val left = zz2.left as TypecastExpression
|
||||
val right = zz2.right as PrefixExpression
|
||||
left.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
zz2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE // 'or' causes UBYTE result
|
||||
}
|
||||
|
||||
test("type cast from byte to ubyte as desired target type") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
ubyte r
|
||||
ubyte ub = (cos8(r)/2 + 100) as ubyte
|
||||
}""")
|
||||
val module = parseModule(src)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val stmts = (module.statements.single() as Block).statements
|
||||
stmts.size shouldBe 2
|
||||
val ubexpr = (stmts[1] as VarDecl).value as TypecastExpression
|
||||
ubexpr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
|
||||
test("assignment isAugmented correctness") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte r
|
||||
ubyte q
|
||||
r = q*3 ; #1 no
|
||||
r = r*3 ; #2 yes
|
||||
r = 3*r ; #3 yes
|
||||
r = 3*q ; #4 no
|
||||
r = 5+r ; #5 yes
|
||||
r = 5-r ; #6 no
|
||||
r = r-5 ; #7 yes
|
||||
r = not r ; #8 yes
|
||||
r = not q ; #9 no
|
||||
r = (q+r)+5 ; #10 yes
|
||||
r = q+(r+5) ; #11 yes
|
||||
r = (q+r)-5 ; #12 yes
|
||||
r = q+(r-5) ; #13 yes
|
||||
}
|
||||
}""")
|
||||
|
||||
val module = parseModule(src)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val stmts = program.entrypoint.statements
|
||||
val expectedResults = listOf(
|
||||
false, true, true,
|
||||
false, true, false,
|
||||
true, true, false,
|
||||
true, true, true,
|
||||
true
|
||||
)
|
||||
stmts.size shouldBe 15
|
||||
expectedResults.size shouldBe stmts.size-2
|
||||
for((idx, pp) in stmts.drop(2).zip(expectedResults).withIndex()) {
|
||||
val assign = pp.first as Assignment
|
||||
val expected = pp.second
|
||||
withClue("#${idx+1}: should${if(expected) "" else "n't"} be augmentable: $assign") {
|
||||
assign.isAugmentable shouldBe expected
|
||||
assign.value shouldBe (instanceOf<PrefixExpression>() or instanceOf<BinaryExpression>() or instanceOf<TypecastExpression>())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
109
compilerAst/test/TestProgram.kt
Normal file
109
compilerAst/test/TestProgram.kt
Normal file
@ -0,0 +1,109 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeIn
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.ast.helpers.DummyFunctions
|
||||
import prog8tests.ast.helpers.DummyMemsizer
|
||||
import prog8tests.ast.helpers.DummyStringEncoder
|
||||
|
||||
class TestProgram: FunSpec({
|
||||
|
||||
context("Constructor") {
|
||||
test("withNameBuiltinsAndMemsizer") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.modules.size shouldBe 1
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
program.modules[0].program shouldBeSameInstanceAs program
|
||||
program.modules[0].parent shouldBeSameInstanceAs program.namespace
|
||||
}
|
||||
}
|
||||
|
||||
context("AddModule") {
|
||||
test("withEmptyModule") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
|
||||
val retVal = program.addModule(m1)
|
||||
|
||||
retVal shouldBeSameInstanceAs program
|
||||
program.modules.size shouldBe 2
|
||||
m1 shouldBeIn program.modules
|
||||
m1.program shouldBeSameInstanceAs program
|
||||
m1.parent shouldBeSameInstanceAs program.namespace
|
||||
|
||||
withClue("module may not occur multiple times") {
|
||||
val ex = shouldThrow<IllegalArgumentException> { program.addModule(m1) }
|
||||
ex.message shouldContain m1.name
|
||||
}
|
||||
|
||||
val m2 = Module(mutableListOf(), m1.position, m1.source)
|
||||
withClue("other module but with same name may not occur multiple times") {
|
||||
val ex = shouldThrow<IllegalArgumentException> { program.addModule(m2) }
|
||||
ex.message shouldContain m1.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("MoveModuleToFront") {
|
||||
test("withInternedStringsModule") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m = program.modules[0]
|
||||
m.name shouldBe internedStringsModuleName
|
||||
|
||||
val retVal = program.moveModuleToFront(m)
|
||||
retVal shouldBeSameInstanceAs program
|
||||
program.modules[0] shouldBeSameInstanceAs m
|
||||
}
|
||||
|
||||
test("withForeignModule") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
|
||||
shouldThrow<IllegalArgumentException> { program.moveModuleToFront(m) }
|
||||
}
|
||||
|
||||
test("withFirstOfPreviouslyAddedModules") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
|
||||
program.addModule(m1)
|
||||
program.addModule(m2)
|
||||
|
||||
val retVal = program.moveModuleToFront(m1)
|
||||
retVal shouldBeSameInstanceAs program
|
||||
program.modules.indexOf(m1) shouldBe 0
|
||||
}
|
||||
|
||||
test("withSecondOfPreviouslyAddedModules") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
|
||||
program.addModule(m1)
|
||||
program.addModule(m2)
|
||||
|
||||
val retVal = program.moveModuleToFront(m2)
|
||||
retVal shouldBeSameInstanceAs program
|
||||
program.modules.indexOf(m2) shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
context("Properties") {
|
||||
test("modules") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
|
||||
val ms1 = program.modules
|
||||
val ms2 = program.modules
|
||||
ms2 shouldBeSameInstanceAs ms1
|
||||
}
|
||||
}
|
||||
})
|
@ -1,9 +1,9 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.core.StringStartsWith
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.AnnotationSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.parser.SourceCode
|
||||
import prog8.parser.SourceCode.Companion.libraryFilePrefix
|
||||
import prog8tests.ast.helpers.assumeNotExists
|
||||
@ -11,11 +11,9 @@ import prog8tests.ast.helpers.assumeReadableFile
|
||||
import prog8tests.ast.helpers.fixturesDir
|
||||
import prog8tests.ast.helpers.resourcesDir
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestSourceCode {
|
||||
class TestSourceCode: AnnotationSpec() {
|
||||
|
||||
@Test
|
||||
fun testFromString() {
|
||||
@ -25,30 +23,30 @@ class TestSourceCode {
|
||||
val src = SourceCode.Text(text)
|
||||
val actualText = src.getCharStream().toString()
|
||||
|
||||
assertContains(src.origin, Regex("^<String@[0-9a-f\\-]+>$"))
|
||||
assertEquals(text, actualText)
|
||||
assertFalse(src.isFromResources)
|
||||
assertFalse(src.isFromFilesystem)
|
||||
assertThat(src.toString(), StringStartsWith("prog8.parser.SourceCode"))
|
||||
src.origin shouldContain Regex("^<String@[0-9a-f\\-]+>$")
|
||||
actualText shouldBe text
|
||||
src.isFromResources shouldBe false
|
||||
src.isFromFilesystem shouldBe false
|
||||
src.toString().startsWith("prog8.parser.SourceCode") shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithNonExistingPath() {
|
||||
val filename = "i_do_not_exist.p8"
|
||||
val path = assumeNotExists(fixturesDir, filename)
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.File(path) }
|
||||
shouldThrow<NoSuchFileException> { SourceCode.File(path) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithMissingExtension_p8() {
|
||||
val pathWithoutExt = assumeNotExists(fixturesDir,"simple_main")
|
||||
assumeReadableFile(fixturesDir,"simple_main.p8")
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
|
||||
shouldThrow<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithDirectory() {
|
||||
assertFailsWith<AccessDeniedException> { SourceCode.File(fixturesDir) }
|
||||
shouldThrow<AccessDeniedException> { SourceCode.File(fixturesDir) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -57,10 +55,10 @@ class TestSourceCode {
|
||||
val path = assumeReadableFile(fixturesDir, filename)
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
assertEquals(expectedOrigin, src.origin)
|
||||
assertEquals(path.toFile().readText(), src.readText())
|
||||
assertFalse(src.isFromResources)
|
||||
assertTrue(src.isFromFilesystem)
|
||||
src.origin shouldBe expectedOrigin
|
||||
src.readText() shouldBe path.toFile().readText()
|
||||
src.isFromResources shouldBe false
|
||||
src.isFromFilesystem shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -70,8 +68,8 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(path).toFile()
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
assertEquals(expectedOrigin, src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
src.origin shouldBe expectedOrigin
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -80,10 +78,10 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix/$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
assertTrue(src.isFromResources)
|
||||
assertFalse(src.isFromFilesystem)
|
||||
src.origin shouldBe "$libraryFilePrefix/$pathString"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
src.isFromResources shouldBe true
|
||||
src.isFromFilesystem shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -92,8 +90,8 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
src.origin shouldBe "$libraryFilePrefix$pathString"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -102,9 +100,9 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix/$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
assertTrue(src.isFromResources, ".isFromResources")
|
||||
src.origin shouldBe "$libraryFilePrefix/$pathString"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
src.isFromResources shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -113,8 +111,8 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
src.origin shouldBe "$libraryFilePrefix$pathString"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -123,9 +121,9 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix/prog8lib/math.p8", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
assertTrue(src.isFromResources, ".isFromResources")
|
||||
src.origin shouldBe "$libraryFilePrefix/prog8lib/math.p8"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
src.isFromResources shouldBe true
|
||||
}
|
||||
|
||||
|
||||
@ -134,13 +132,13 @@ class TestSourceCode {
|
||||
val pathString = "/prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString.substring(1))
|
||||
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
@Test
|
||||
fun testFromResourcesWithNonExistingFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString)
|
||||
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,15 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.core.spec.style.AnnotationSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestSubroutines {
|
||||
class TestSubroutines: AnnotationSpec() {
|
||||
|
||||
@Test
|
||||
fun stringParameterAcceptedInParser() {
|
||||
@ -33,12 +29,12 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.isEmpty())
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, func.parameters.single().type)
|
||||
assertTrue(func.statements.isEmpty())
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.STR
|
||||
asmfunc.statements.isEmpty() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
func.parameters.single().type shouldBe DataType.STR
|
||||
func.statements.isEmpty() shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -59,11 +55,11 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.ARRAY_UB, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.isEmpty())
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
|
||||
assertTrue(func.statements.isEmpty())
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.ARRAY_UB
|
||||
asmfunc.statements.isEmpty() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
func.parameters.single().type shouldBe DataType.ARRAY_UB
|
||||
func.statements.isEmpty() shouldBe true
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user