Compare commits

..
37 Commits
Author SHA1 Message Date
Irmen de Jong 5fd83f2757 version 7.3 2021-11-14 22:55:13 +01:00
Irmen de Jong c80df4140b until-loop condition now also simplified to avoid stack-eval 2021-11-14 22:51:02 +01:00
Irmen de Jong 53e1729e2f introduce option to use internal scratch variables via prog8_lib definitions (ony for compiler, not for user code!) 2021-11-14 16:01:54 +01:00
Irmen de Jong ab2d1122a9 conditional expressions are optimized more intelligently (simple ones are not split off in separate assignments) 2021-11-14 12:38:56 +01:00
Irmen de Jong 5190594c8a added several more assembly-level optimizations to remove redundant instructions 2021-11-14 12:23:46 +01:00
Irmen de Jong c858ceeb58 compiler shouldn't use cx16.r15 as temp var 2021-11-14 02:38:59 +01:00
Irmen de Jong f0f52b9166 optimize typecasted binary expression to avoid even more estack use. also fix wrong parent crash in removal of unused variable's assignments. 2021-11-13 14:22:37 +01:00
Irmen de Jong 00c6f74481 tweak temp float 2021-11-13 12:56:59 +01:00
Irmen de Jong 2177ba0ed2 added signed versions of the cx16 virtual registers 2021-11-13 02:42:21 +01:00
Irmen de Jong 3483515346 preparing for more optimizations 2021-11-12 23:23:51 +01:00
Irmen de Jong 75a06d2a40 preparing for more optimizations 2021-11-12 02:17:37 +01:00
Irmen de Jong 53ac11983b better unused variable removal 2021-11-11 03:03:21 +01:00
Irmen de Jong 69f4a4d4f8 tweak expr.typecastTo() a bit 2021-11-11 00:15:09 +01:00
Irmen de Jong 222bcb808f optimize load-store-load combo in output asm 2021-11-10 23:47:35 +01:00
Irmen de Jong 686483f51a fixed division of signed byte number by 2. (!) 2021-11-10 00:17:56 +01:00
Irmen de Jong 8df3da11e3 add cosr8, sinr8, cosr16 and sinr16 builtin functions that take a degree 0..179 (= 0..358 in 2 degree steps)
to more easily scale halves/quarters etc of a circle than possible with the ones that take 0..255 'degrees'.
2021-11-09 23:39:26 +01:00
Irmen de Jong 84dafda0e4 fix error message for type mismatch on builtin-function parameter 2021-11-09 22:19:07 +01:00
Irmen de Jong b909facfe5 fix compiler stackoverflow crash on certain typecasted expressions containing floats. 2021-11-09 19:31:19 +01:00
Irmen de Jong 7780d94de1 discovered crash related to float typecasting in asm assignment codegen 2021-11-09 03:45:07 +01:00
Irmen de Jong f2c440e466 new sin/cos idea 2021-11-09 02:38:43 +01:00
Irmen de Jong 4937e004b5 fix compiler crash where it used wrong datatype in split assignment
fixes crash for "ubyte bb ;; uword ww ;; bb = not bb or not ww"
2021-11-09 01:13:23 +01:00
Irmen de Jong 4cb383dccb discovered crash about storage size mismatch 2021-11-08 21:44:06 +01:00
Irmen de Jong c8a4b6f23c refactor expressionsAsmGen so that it now has just 1 single public function
this makes replacing it by a non-stack based solution easier in the future.
2021-11-08 19:21:55 +01:00
Irmen de Jong 857724c7e6 attempt to make if-statement not use stack eval anymore 2021-11-08 19:07:36 +01:00
Irmen de Jong a9b0400d13 fixed 'not' operator priority: it now has higher priority as or/and/xor. 2021-11-08 18:38:04 +01:00
Irmen de Jong 2d1e5bbc7e remove unimportant empty tests 2021-11-08 17:00:10 +01:00
Irmen de Jong 60627ce756 kotest migration done, fixes #70 2021-11-08 16:19:24 +01:00
Irmen de Jong 7961a09d16 converting compiler module's testcases to kotest assertions 2021-11-08 16:14:22 +01:00
Irmen de Jong 613efcacc7 converting compiler module's testcases to kotest (ongoing) 2021-11-08 15:08:48 +01:00
Irmen de Jong 7e8db16e18 moved to kotest assertions in compilerAst module tests 2021-11-07 21:18:18 +01:00
Irmen de Jong 1fbbed7e23 remove unittest machinery from modules that don't have tests 2021-11-07 17:34:14 +01:00
Irmen de Jong 984272beb4 migrated compilerAst module to KoTest (but not finished with the assertions yet) 2021-11-07 17:25:53 +01:00
Irmen de Jong b9ce94bb68 migrated codeGeneration module to KoTest 2021-11-07 15:40:05 +01:00
Irmen de Jong f4c4ee78d9 re-use global returnvalue temp var instead of duplicating it in every subroutine that needs it 2021-11-07 14:19:21 +01:00
Irmen de Jong 793596614e attempt to fix ReadTheDocs build issue 2021-11-07 00:37:31 +01:00
Irmen de Jong 136280100c attempt to fix ReadTheDocs build issue 2021-11-07 00:23:44 +01:00
Irmen de Jong 29f1e4d2c9 attempt to fix ReadTheDocs build issue 2021-11-07 00:18:51 +01:00
89 changed files with 5004 additions and 3830 deletions

No files matched your search

+22
View 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
View 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
View 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>
+4 -1
View File
@@ -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>
+29
View File
@@ -0,0 +1,29 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.9"
# You can also specify other tool versions:
# nodejs: "16"
# rust: "1.55"
# golang: "1.17"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
formats:
- pdf
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt
+2 -6
View File
@@ -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.
+2 -2
View File
@@ -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>
File diff suppressed because it is too large. Load diff
@@ -118,15 +118,15 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
// @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...
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")) {
@@ -136,8 +136,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
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))
mods.add(Modification(lines[4].index, true, null))
mods.add(Modification(lines[5].index, true, null))
}
}
@@ -146,7 +146,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
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))
mods.add(Modification(lines[2].index, true, null))
}
}
@@ -154,12 +154,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,14 +167,128 @@ 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) {
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) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
}
}
}
/*
sta A1
<other st/ld instruction not involving A1>
sta A1 ; can be removed
*/
if(!overlappingMods && first.startsWith("st") && third.startsWith("st")) {
val reg1 = first[2]
val reg3 = third[2]
if(reg1==reg3 && (second.startsWith("ld") || second.startsWith("st"))) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==thirdvalue && firstvalue!=secondvalue) {
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) {
overlappingMods = true
val reg2 = second[2]
mods.add(Modification(lines[1].index, false, " ta$reg2"))
mods.add(Modification(lines[2].index, true, null))
}
}
}
return mods
}
@@ -182,9 +296,9 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
// 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,14 +310,25 @@ 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
true
}
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))
}
}
}
@@ -213,15 +338,15 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
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)
|| (" 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))
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
}
}
return mods
@@ -230,12 +355,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
@@ -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",
@@ -283,11 +285,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)
}
File diff suppressed because it is too large. Load diff
@@ -209,7 +209,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)
@@ -445,9 +442,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) {
@@ -7,12 +7,10 @@ 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) {
@@ -105,7 +103,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
throw FatalAstException("assignment should be augmentable $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
@@ -657,14 +669,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 +685,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 +706,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 +716,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 +728,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 +742,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
@@ -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,7 +43,7 @@ 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)
@@ -80,7 +73,7 @@ locallabel:
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 +81,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
View 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)
}
+1 -23
View File
@@ -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!
-2
View File
@@ -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
@@ -17,34 +20,14 @@ import prog8.compilerinterface.isInRegularRAMof
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,
@@ -78,10 +61,38 @@ 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 tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)
val tempVar = when(tempDt) {
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
}
@@ -15,8 +15,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,23 +22,6 @@ 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.
@@ -439,12 +420,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)
@@ -107,9 +107,32 @@ class UnusedCodeRemover(private val program: Program,
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 {
if(it.parent is AssignTarget)
it.parent as AssignTarget
else if(it.parent.parent is AssignTarget)
it.parent.parent as AssignTarget
else null
}.filter {
it.isInRegularRAMof(compTarget.machine)
}
if(assignTargets.size==usages.size) {
// TODO FIX THAT A MEMREAD OF THE VARIABLE ISN'T RECOGNISED AS A USE (imageviewer iff_module.p8 pixptr)
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)
)
}
}
}
}
+2 -4
View File
@@ -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 {
+2 -8
View File
@@ -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>
+1 -1
View File
@@ -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 ----
+51
View File
@@ -699,6 +699,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 +749,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
}
+1 -1
View File
@@ -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 ----
+51 -1
View File
@@ -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
+143
View File
@@ -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
+18 -6
View File
@@ -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
View File
@@ -1 +1 @@
7.2
7.3
@@ -11,6 +11,7 @@ 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.*
@@ -45,14 +46,18 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
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))
@@ -141,7 +146,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 +206,84 @@ 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 &&
if((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")
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 dt = expr.left.inferType(program)
val name = 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")
else -> throw AssemblyError("invalid dt")
}
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 +301,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 &&
(binExpr.right as? NumericLiteralValue)?.number!=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 +338,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 &&
(binExpr.right as? NumericLiteralValue)?.number!=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 +363,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 +435,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))
+3 -1
View File
@@ -44,6 +44,8 @@ fun compileProgram(filepath: Path,
lateinit var program: Program
lateinit var importedFiles: List<Path>
val optimizeFloatExpr = if(optimize) optimizeFloatExpressions else false
val compTarget =
when(compilationTarget) {
C64Target.name -> C64Target
@@ -58,7 +60,7 @@ fun compileProgram(filepath: Path,
with(compilationOptions) {
this.slowCodegenWarnings = slowCodegenWarnings
this.optimize = optimize
this.optimizeFloatExpressions = optimizeFloatExpressions
this.optimizeFloatExpressions = optimizeFloatExpr
}
program = programresult
importedFiles = imported
@@ -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)
@@ -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) {
@@ -430,7 +429,7 @@ internal class AstChecker(private val program: Program,
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)
errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position)
}
if(assignment.value is TypecastExpression) {
@@ -53,7 +53,6 @@ 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()
@@ -170,8 +169,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 +179,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))
}
}
}
@@ -47,7 +47,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
val rightDt = expr.right.inferType(program)
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
// 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(
@@ -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
}
@@ -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"
}
}
}
}
+132 -180
View File
@@ -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
View 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
// }
// }){}
// )
// }
//}
+39 -32
View File
@@ -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
}
}
}
})
+32 -36
View File
@@ -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.toShort() shouldBe platform.encodeString("\n", false)[0]
}
@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.toShort() shouldBe platform.encodeString("\n", false)[0]
}
@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.toShort() shouldBe platform.encodeString("\n", false)[0]
}
is NumericLiteralValue -> {
assertEquals(
platform.encodeString("\n", false)[0],
arg.number.toShort())
arg.number.toShort() shouldBe platform.encodeString("\n", false)[0]
}
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
}
}
}
})
+103 -82
View File
@@ -1,18 +1,13 @@
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.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 +17,97 @@ 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) = compileProgram(
filepath,
optimize,
optimizeFloatExpressions = true,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = target.name,
sourceDirs = listOf(),
outputDir
)
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 +130,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"
)
}
}
}
}
})
+61 -76
View File
@@ -1,26 +1,23 @@
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 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.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 +25,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 +50,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 +82,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 +123,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 +154,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 +182,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 +208,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 +226,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 +250,16 @@ 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()
}
}
})
+13 -25
View File
@@ -1,9 +1,6 @@
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.compileProgram
import prog8.compiler.target.Cx16Target
import prog8tests.ast.helpers.assumeReadableFile
@@ -22,13 +19,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,12 +33,11 @@ class TestCompilerOptionSourcedirs {
""")}
}
@AfterAll
fun tearDown() {
afterSpec {
tempFileInWorkingDir.deleteExisting()
}
private fun compileFile(filePath: Path, sourceDirs: List<String>) =
fun compileFile(filePath: Path, sourceDirs: List<String>) =
compileProgram(
filepath = filePath,
optimize = false,
@@ -56,47 +50,41 @@ class TestCompilerOptionSourcedirs {
outputDir
)
@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
}
}
}
})
+46 -60
View File
@@ -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
@@ -18,74 +18,53 @@ 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() {
test("testInValidRamC64_memory_addresses") {
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testNotInValidRamC64_memory_addresses() {
test("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))
target.isInRegularRAMof(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assertFalse(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(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))
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
@Test
fun testInValidRamC64_memory_identifiers() {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
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))
}
private fun createTestProgramForMemoryRefViaVar(program: Program, address: Int, vartype: VarDeclType): AssignTarget {
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)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
@@ -95,16 +74,28 @@ class TestMemory {
module.linkIntoProgram(program)
return target
}
test("testInValidRamC64_memory_identifiers") {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
@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))
target.isInRegularRAMof(C64Target.machine) shouldBe true
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
target.isInRegularRAMof(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
target.isInRegularRAMof(C64Target.machine) shouldBe true
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
target.isInRegularRAMof(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
@Test
fun testInValidRamC64_variable() {
test("testInValidRamC64_memory_expression") {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
test("testInValidRamC64_variable") {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, 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)
@@ -113,11 +104,10 @@ class TestMemory {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testInValidRamC64_memmap_variable() {
test("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)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
@@ -127,11 +117,10 @@ class TestMemory {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testNotInValidRamC64_memmap_variable() {
test("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)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
@@ -141,11 +130,10 @@ class TestMemory {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
@Test
fun testInValidRamC64_array() {
test("testInValidRamC64_array") {
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, 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)
@@ -155,11 +143,10 @@ class TestMemory {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testInValidRamC64_array_memmapped() {
test("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)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
@@ -170,11 +157,10 @@ class TestMemory {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
@Test
fun testNotValidRamC64_array_memmapped() {
test("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)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
@@ -185,6 +171,6 @@ class TestMemory {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine))
target.isInRegularRAMof(C64Target.machine) shouldBe false
}
}
})
+94 -100
View File
@@ -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(0x00, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(3.141592653) shouldBe Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)
Mflpt5.fromNumber(3.141592653589793) shouldBe Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)
Mflpt5.fromNumber(32768) shouldBe Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(-32768) shouldBe Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(1) shouldBe Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(0.7071067812) shouldBe Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)
Mflpt5.fromNumber(0.7071067811865476) shouldBe Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)
Mflpt5.fromNumber(1.4142135624) shouldBe Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)
Mflpt5.fromNumber(1.4142135623730951) shouldBe Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)
Mflpt5.fromNumber(-.5) shouldBe Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(0.69314718061) shouldBe Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)
Mflpt5.fromNumber(0.6931471805599453) shouldBe Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)
Mflpt5.fromNumber(10) shouldBe Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(1000000000) shouldBe Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)
Mflpt5.fromNumber(.5) shouldBe Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(1.4426950408889634) shouldBe Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)
Mflpt5.fromNumber(1.5707963267948966) shouldBe Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)
Mflpt5.fromNumber(6.283185307179586) shouldBe Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)
Mflpt5.fromNumber(.25) shouldBe Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(123.45678e22) shouldBe Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)
Mflpt5.fromNumber(-123.45678e-22) shouldBe Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)
}
@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(0xff, 0x7f, 0xff, 0xff, 0xff)
Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)
Mflpt5.fromNumber(1.7e-38) shouldBe Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)
Mflpt5.fromNumber(-1.7e-39) shouldBe Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)
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(0x00, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe 0.0
Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble() shouldBe(3.141592653 plusOrMinus epsilon)
Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble() shouldBe(3.141592653589793 plusOrMinus epsilon)
Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe 32768.0
Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble() shouldBe -32768.0
Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe 1.0
Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble() shouldBe(0.7071067812 plusOrMinus epsilon)
Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble() shouldBe(0.7071067811865476 plusOrMinus epsilon)
Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble() shouldBe(1.4142135624 plusOrMinus epsilon)
Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble() shouldBe(1.4142135623730951 plusOrMinus epsilon)
Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble() shouldBe -.5
Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble() shouldBe(0.69314718061 plusOrMinus epsilon)
Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble() shouldBe(0.6931471805599453 plusOrMinus epsilon)
Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble() shouldBe 10.0
Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble() shouldBe 1000000000.0
Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe .5
Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble() shouldBe(1.4426950408889634 plusOrMinus epsilon)
Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble() shouldBe(1.5707963267948966 plusOrMinus epsilon)
Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble() shouldBe(6.283185307179586 plusOrMinus epsilon)
Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe .25
Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble() shouldBe(123.45678e22 plusOrMinus 1.0e15)
Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble() shouldBe(-123.45678e-22 plusOrMinus epsilon)
}
}
})
+86 -96
View File
@@ -1,92 +1,85 @@
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 io.kotest.matchers.shouldNotBe
import prog8.ast.base.DataType
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() {
test("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)
(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, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, 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("testEqualsAndNotEquals") {
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) == NumericLiteralValue(DataType.UWORD, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) == NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) == NumericLiteralValue(DataType.UBYTE, 254, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) == NumericLiteralValue(DataType.UWORD, 12345, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) == NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 22239, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) == NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
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)))
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos)) shouldBe false
sameValueAndType(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))
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) != NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) != NumericLiteralValue(DataType.UWORD, 101, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) != NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 245, dummyPos) != NumericLiteralValue(DataType.UBYTE, 246, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) != NumericLiteralValue(DataType.UWORD, 12346, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) != NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UBYTE, 9, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UWORD, 9, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.FLOAT, 9.0, 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)))
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, 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)
@@ -98,47 +91,44 @@ class TestNumericLiteralValue {
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, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, 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, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, 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, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, 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, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, 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, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, 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, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, 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, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, 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, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe false
}
}
})
+251 -50
View File
@@ -1,26 +1,32 @@
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.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.printAst
import prog8.compiler.target.C64Target
import prog8.compilerinterface.*
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("testRemoveEmptySubroutineExceptStart") {
val sourcecode = """
main {
sub start() {
@@ -34,13 +40,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("testDontRemoveEmptySubroutineIfItsReferenced") {
val sourcecode = """
main {
sub start() {
@@ -57,45 +66,44 @@ 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()
{
test("testGeneratedConstvalueInheritsProperParentLinkage") {
val number = NumericLiteralValue(DataType.UBYTE, 11, 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.toInt() shouldBe 11
constvalue.type shouldBe DataType.BYTE
tc shouldBeSameInstanceAs constvalue.parent
}
@Test
fun testConstantFoldedAndSilentlyTypecastedForInitializerValues() {
test("testConstantFoldedAndSilentlyTypecastedForInitializerValues") {
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 +113,212 @@ 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.toDouble() 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.toDouble() shouldBe 11.0
(initX2.value as NumericLiteralValue).type shouldBe DataType.BYTE
(initX2.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
(initY1.value as NumericLiteralValue).type shouldBe DataType.UBYTE
(initY1.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
(initY2.value as NumericLiteralValue).type shouldBe DataType.UBYTE
(initY2.value as NumericLiteralValue).number.toDouble() 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
ff += (ub as float) ; operator doesn't matter
}
}
"""
val result1 = compileText(C64Target, optimize=false, src, writeAssembly = false).assertSuccess()
val assignYY = result1.program.entrypoint.statements.last() as Assignment
assignYY.isAugmentable shouldBe true
assignYY.target.identifier!!.nameInSource shouldBe listOf("ff")
val value = assignYY.value as BinaryExpression
value.operator shouldBe "+"
value.left shouldBe IdentifierReference(listOf("ff"), Position.DUMMY)
value.right shouldBe instanceOf<TypecastExpression>()
val asm = generateAssembly(result1.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")
}
})
+126 -133
View File
@@ -1,94 +1,91 @@
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.throwables.shouldThrow
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<Short>(0))
Petscii.encodePetscii("\u0000", false) shouldBe Ok(listOf<Short>(0))
Petscii.decodePetscii(listOf(0), true) shouldBe "\u0000"
Petscii.decodePetscii(listOf(0), 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<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))
Petscii.encodePetscii("\uf11a", true) shouldBe Ok(listOf<Short>(0x12)) // reverse vid
Petscii.encodePetscii("✓", true) shouldBe Ok(listOf<Short>(0xfa))
withClue("expect lowercase error fallback") {
Petscii.encodePetscii("π", true) shouldBe Ok(listOf<Short>(255))
Petscii.encodePetscii("♥", true) shouldBe Ok(listOf<Short>(0xd3))
}
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), 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<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))
Petscii.encodePetscii("\uf11a") shouldBe Ok(listOf<Short>(0x12)) // reverse vid
Petscii.encodePetscii("♥") shouldBe Ok(listOf<Short>(0xd3))
Petscii.encodePetscii("π") shouldBe Ok(listOf<Short>(0xff))
withClue("expecting fallback") {
Petscii.encodePetscii("✓") shouldBe Ok(listOf<Short>(250))
}
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)) shouldBe "H£♥π"
}
@Test
fun testScreencodeLowercase() {
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
test("testScreencodeLowercase") {
Petscii.encodeScreencode("hello WORLD 123 @!£", true) shouldBe
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))))
Petscii.encodeScreencode("✓", true) shouldBe Ok(listOf<Short>(0x7a))
withClue("expect fallback") {
Petscii.encodeScreencode("♥", true) shouldBe Ok(listOf<Short>(83))
Petscii.encodeScreencode("π", true) shouldBe Ok(listOf<Short>(94))
}
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), 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<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
Petscii.encodeScreencode("♥") shouldBe Ok(listOf<Short>(0x53))
Petscii.encodeScreencode("π") shouldBe Ok(listOf<Short>(0x5e))
Petscii.encodeScreencode("HELLO") shouldBe Ok(listOf<Short>(8, 5, 12, 12, 15))
withClue("expecting fallback") {
Petscii.encodeScreencode("hello") shouldBe Ok(listOf<Short>(8, 5, 12, 12, 15))
Petscii.encodeScreencode("✓") shouldBe Ok(listOf<Short>(122))
}
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)) 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) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), true) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), true) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), false) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), false) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), true) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), true) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), false) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), false) }
Petscii.scr2petscii(-1).expectError { "-1 should error" }
Petscii.scr2petscii(256).expectError { "256 should error" }
@@ -98,9 +95,7 @@ class TestPetscii {
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 +104,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 94
encodeP('^', true) shouldBe 94
encodeS('^', false) shouldBe 30
encodeS('^', true) shouldBe 30
encodeP('_', false) shouldBe 228
encodeP('_', true) shouldBe 228
encodeS('_', false) shouldBe 100
encodeS('_', true) shouldBe 100
encodeP('{', false) shouldBe 243
encodeP('{', true) shouldBe 243
encodeS('{', false) shouldBe 115
encodeS('{', true) shouldBe 115
encodeP('}', false) shouldBe 235
encodeP('}', true) shouldBe 235
encodeS('}', false) shouldBe 107
encodeS('}', true) shouldBe 107
encodeP('|', false) shouldBe 221
encodeP('|', true) shouldBe 221
encodeS('|', false) shouldBe 93
encodeS('|', true) shouldBe 93
encodeP('\\', false) shouldBe 205
encodeP('\\', true) shouldBe 205
encodeS('\\', false) shouldBe 77
encodeS('\\', true) shouldBe 77
}
@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 221
encodeP('|', true) shouldBe 221
encodeS('|', false) shouldBe 93
encodeS('|', true) shouldBe 93
// ... 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 221
encodeP('│', true) shouldBe 221
encodeS('│', false) shouldBe 93
encodeS('│', true) shouldBe 93
// underscore
assertEquals(228, encodeP('_', false))
assertEquals(228, encodeP('_', true))
assertEquals(100, encodeS('_', false))
assertEquals(100, encodeS('_', true))
encodeP('_', false) shouldBe 228
encodeP('_', true) shouldBe 228
encodeS('_', false) shouldBe 100
encodeS('_', true) shouldBe 100
// ... 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 228
encodeP('▁', true) shouldBe 228
encodeS('▁', false) shouldBe 100
encodeS('▁', true) shouldBe 100
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
assertEquals(192, encodeP('─', false))
assertEquals(192, encodeP('─', true))
assertEquals(64, encodeS('─', false))
assertEquals(64, encodeS('─', true))
encodeP('─', false) shouldBe 192
encodeP('─', true) shouldBe 192
encodeS('─', false) shouldBe 64
encodeS('─', true) shouldBe 64
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
assertEquals(221, encodeP('│', false))
assertEquals(221, encodeP('│', true))
assertEquals(93, encodeS('│', false))
assertEquals(93, encodeS('│', true))
encodeP('│', false) shouldBe 221
encodeP('│', true) shouldBe 221
encodeS('│', false) shouldBe 93
encodeS('│', true) shouldBe 93
}
@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(195), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodePetscii(listOf(195), true).single() shouldBe 'C'
Petscii.decodePetscii(listOf(192), false).single() shouldBe '─'
Petscii.decodePetscii(listOf(192), true).single() shouldBe '─'
Petscii.decodeScreencode(listOf(67), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodeScreencode(listOf(67), true).single() shouldBe 'C'
Petscii.decodeScreencode(listOf(64), false).single() shouldBe '─'
Petscii.decodeScreencode(listOf(64), 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(125), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(125), true).single() shouldBe '│'
Petscii.decodePetscii(listOf(221), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(221), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93), false).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(66), false).single() shouldBe '\uf13c' // "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)"
Petscii.decodeScreencode(listOf(66), true).single() shouldBe 'B'
}
}
})
+33 -23
View File
@@ -1,7 +1,10 @@
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.types.instanceOf
import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.ast.GlobalNamespace
import prog8.ast.base.ParentSentinel
import prog8.ast.expressions.NumericLiteralValue
@@ -9,14 +12,11 @@ import prog8.ast.statements.*
import prog8.compiler.target.C64Target
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("testModulesParentIsGlobalNamespace") {
val src = """
main {
sub start() {
@@ -26,13 +26,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("testAnonScopeVarsMovedIntoSubroutineScope") {
val src = """
main {
sub start() {
@@ -49,19 +48,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("testLabelsWithAnonScopes") {
val src = """
main {
sub start() {
@@ -120,7 +128,9 @@ 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
}
}
}
})
+87 -78
View File
@@ -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,30 @@ 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)
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.size shouldBe 4
val paramvar = func.statements[0] as VarDecl
assertEquals("thing", paramvar.name)
assertEquals(DataType.STR, paramvar.datatype)
paramvar.name shouldBe "thing"
paramvar.datatype shouldBe DataType.STR
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 not be transformed by normal compiler steps") {
assign.value shouldBe instanceOf<TypecastExpression>()
}
(assign.value as TypecastExpression).type shouldBe DataType.UWORD
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 +97,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("arrayParameterNotYetAllowed_ButShouldPerhapsBe") {
// note: the *parser* accepts this as it is valid *syntax*,
// however, it's not (yet) valid for the compiler
val text = """
@@ -135,13 +145,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,16 +179,15 @@ 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("testUwordParameterAndNormalVarIndexedAsArrayWorkAsDirectMemoryRead") {
val text="""
main {
sub thing(uword rr) {
@@ -201,29 +209,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 10 // rr, xx, xx assign, yy, 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)
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("testUwordParameterAndNormalVarIndexedAsArrayWorkAsMemoryWrite") {
val text="""
main {
sub thing(uword rr) {
@@ -241,15 +250,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
}
}
})
+139 -152
View File
@@ -1,7 +1,12 @@
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.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import prog8.ast.base.DataType
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
@@ -9,31 +14,9 @@ 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"
@@ -67,165 +50,172 @@ class TestAbstractZeropage {
}
}
}
test("testAbstractZeropage") {
val compTarget = DummyCompilationTarget()
val zp = DummyZeropage(
CompilationOptions(
OutputType.RAW,
LauncherType.NONE,
ZeropageType.FULL,
listOf((0x50..0x5f)),
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)
zp1.availableBytes() shouldBe 238
50 shouldBeIn zp1.free
100 shouldBeIn zp1.free
49 shouldBeIn zp1.free
101 shouldBeIn zp1.free
200 shouldBeIn zp1.free
255 shouldBeIn zp1.free
199 shouldBeIn 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)
zp2.availableBytes() shouldBe 139
50 shouldNotBeIn zp2.free
100 shouldNotBeIn zp2.free
49 shouldBeIn zp2.free
101 shouldBeIn zp2.free
200 shouldNotBeIn zp2.free
255 shouldNotBeIn zp2.free
199 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 0
}
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 3
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 +224,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 0x04
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0a
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9b
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9e
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbe
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0e
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9
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 + 1 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 + 1 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
0x22 shouldBeIn zp1.free
0x80 shouldBeIn zp1.free
0xff shouldBeIn zp1.free
0x02 shouldNotBeIn zp1.free
0x21 shouldNotBeIn zp1.free
}
}
})
@@ -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>()
+31 -10
View File
@@ -1,24 +1,31 @@
package prog8tests.helpers
import io.kotest.assertions.withClue
import io.kotest.matchers.shouldBe
import prog8.ast.Program
import prog8.compiler.CompilationResult
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,14 +40,15 @@ 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(
filepath,
optimize,
optimizeFloatExpressions = false,
optimizeFloatExpressions = optFloatExpr,
writeAssembly = writeAssembly,
slowCodegenWarnings = false,
quietAssembler = true,
@@ -61,10 +69,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()
}
+3 -7
View File
@@ -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.
+2 -2
View File
@@ -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>
@@ -62,6 +62,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)
}
}
@@ -106,7 +118,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)"
@@ -158,7 +170,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 +192,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
+8
View 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)
}
+12 -21
View File
@@ -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'")
}
}
+264 -205
View File
@@ -1,18 +1,21 @@
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.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 +27,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 +59,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 +82,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 +116,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 +133,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 +149,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 +163,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 +200,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 +220,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 +234,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 +249,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 +259,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 +282,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 +300,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 +338,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 +374,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 +405,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 +422,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 +438,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 +457,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 +476,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 +507,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() {
test("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)
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 +579,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 +621,105 @@ 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)
module.linkIntoProgram(program)
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)
module.linkIntoProgram(program)
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)
module.linkIntoProgram(program)
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
}
})
+109
View 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
}
}
})
+35 -37
View File
@@ -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) }
}
}
+15 -19
View File
@@ -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
}
}
-118
View File
@@ -1,118 +0,0 @@
package prog8tests.ast.ast
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
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
import kotlin.test.assertContains
import kotlin.test.assertFailsWith
import kotlin.test.assertSame
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ProgramTests {
@Nested
inner class Constructor {
@Test
fun withNameBuiltinsAndMemsizer() {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
assertThat(program.modules.size, equalTo(1))
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
assertSame(program, program.modules[0].program)
assertSame(program.namespace, program.modules[0].parent)
}
}
@Nested
inner class AddModule {
@Test
fun withEmptyModule() {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
val retVal = program.addModule(m1)
assertSame(program, retVal)
assertThat(program.modules.size, equalTo(2))
assertContains(program.modules, m1)
assertSame(program, m1.program)
assertSame(program.namespace, m1.parent)
assertThat("module may not occur multiple times",
assertFailsWith<IllegalArgumentException> { program.addModule(m1) }.message, containsString(m1.name))
val m2 = Module(mutableListOf(), m1.position, m1.source)
assertThat("other module but with same name may not occur multiple times",
assertFailsWith<IllegalArgumentException> { program.addModule(m2) }.message, containsString(m2.name))
}
}
@Nested
inner class MoveModuleToFront {
@Test
fun withInternedStringsModule() {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m = program.modules[0]
assertThat(m.name, equalTo(internedStringsModuleName))
val retVal = program.moveModuleToFront(m)
assertSame(program, retVal)
assertSame(m, program.modules[0])
}
@Test
fun withForeignModule() {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
assertFailsWith<IllegalArgumentException> { program.moveModuleToFront(m) }
}
@Test
fun 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)
assertSame(program, retVal)
assertThat(program.modules.indexOf(m1), equalTo(0))
}
@Test
fun 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)
assertSame(program, retVal)
assertThat(program.modules.indexOf(m2), equalTo(0))
}
}
@Nested
inner class Properties {
@Test
fun modules() {
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val ms1 = program.modules
val ms2 = program.modules
assertSame(ms1, ms2)
}
}
}
@@ -1,5 +1,30 @@
package prog8tests.ast.helpers
fun <T, U> cartesianProduct(c1: Collection<T>, c2: Collection<U>): Sequence<Pair<T, U>> {
return c1.flatMap { lhsElem -> c2.map { rhsElem -> lhsElem to rhsElem } }.asSequence()
}
fun <T, U, V> cartesianProduct(c1: Collection<T>, c2: Collection<U>, c3: Collection<V>): Sequence<Triple<T, U, V>> {
return sequence {
for (a in c1)
for (b in c2)
for (c in c3)
yield(Triple(a,b,c))
}
}
data class Product<out T, out U, out V, out W>(val first: T, val second: U, val third: V, val fourth: W)
fun <T, U, V, W> cartesianProduct(c1: Collection<T>, c2: Collection<U>, c3: Collection<V>, c4: Collection<W>): Sequence<Product<T, U, V, W>> {
return sequence {
for (a in c1)
for (b in c2)
for (c in c3)
for (d in c4)
yield(Product(a,b,c,d))
}
}
fun <A, B, R> mapCombinations(dim1: Iterable<A>, dim2: Iterable<B>, combine2: (A, B) -> R) =
sequence {
for (a in dim1)
+29 -13
View File
@@ -1,49 +1,65 @@
package prog8tests.ast.helpers
import io.kotest.assertions.withClue
import io.kotest.matchers.shouldBe
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.*
import kotlin.test.assertFalse
import kotlin.test.assertTrue
val workingDir = assumeDirectory("").absolute() // Note: "." does NOT work..!
val fixturesDir = assumeDirectory(workingDir,"test/fixtures")
val resourcesDir = assumeDirectory(workingDir,"res")
val outputDir = assumeDirectory(workingDir, "build/tmp/test")
val outputDir: Path = createIfNotExists(workingDir, "build/tmp/test").also{ assumeDirectory(workingDir, "build/tmp/test") }
fun createIfNotExists(workingDir: Path, path: String): Path {
val dir = workingDir / path
if(!dir.toFile().isDirectory)
Files.createDirectories(dir)
return dir
}
fun assumeNotExists(path: Path): Path {
assertFalse(path.exists(), "sanity check: should not exist: ${path.absolute()}")
withClue("sanity check: should not exist: ${path.absolute()}") {
path.exists() shouldBe false
}
return path
}
fun assumeNotExists(pathStr: String): Path = assumeNotExists(Path(pathStr))
fun assumeNotExists(path: Path, other: String): Path = assumeNotExists(path.div(other))
fun assumeNotExists(path: Path, other: String): Path = assumeNotExists(path / other)
fun assumeReadable(path: Path): Path {
assertTrue(path.isReadable(), "sanity check: should be readable: ${path.absolute()}")
withClue("sanity check: should be readable: ${path.absolute()}") {
path.isReadable() shouldBe true
}
return path
}
fun assumeReadableFile(path: Path): Path {
assertTrue(path.isRegularFile(), "sanity check: should be normal file: ${path.absolute()}")
withClue("sanity check: should be normal file: ${path.absolute()}") {
path.isRegularFile() shouldBe true
}
return assumeReadable(path)
}
fun assumeReadableFile(pathStr: String): Path = assumeReadableFile(Path(pathStr))
fun assumeReadableFile(pathStr: String, other: Path): Path = assumeReadableFile(Path(pathStr), other)
fun assumeReadableFile(pathStr: String, other: String): Path = assumeReadableFile(Path(pathStr), other)
fun assumeReadableFile(path: Path, other: String): Path = assumeReadableFile(path.div(other))
fun assumeReadableFile(path: Path, other: Path): Path = assumeReadableFile(path.div(other))
fun assumeReadableFile(path: Path, other: String): Path = assumeReadableFile(path / other)
fun assumeReadableFile(path: Path, other: Path): Path = assumeReadableFile(path / other)
fun assumeDirectory(path: Path): Path {
assertTrue(path.isDirectory(), "sanity check; should be directory: $path")
withClue("sanity check; should be directory: $path") {
path.isDirectory() shouldBe true
}
return path
}
fun assumeDirectory(pathStr: String): Path = assumeDirectory(Path(pathStr))
fun assumeDirectory(path: Path, other: String): Path = assumeDirectory(path.div(other))
fun assumeDirectory(pathStr: String, other: String): Path = assumeDirectory(Path(pathStr).div(other))
fun assumeDirectory(pathStr: String, other: Path): Path = assumeDirectory(Path(pathStr).div(other))
fun assumeDirectory(path: Path, other: String): Path = assumeDirectory(path / other)
fun assumeDirectory(pathStr: String, other: String): Path = assumeDirectory(Path(pathStr) / other)
fun assumeDirectory(pathStr: String, other: Path): Path = assumeDirectory(Path(pathStr) / other)
@Deprecated("Directories are checked automatically at init.",
+145 -198
View File
@@ -1,362 +1,309 @@
package prog8tests.ast
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.jupiter.api.Nested
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.shouldBe
import prog8tests.ast.helpers.*
import kotlin.io.path.Path
import kotlin.io.path.div
import kotlin.test.assertFailsWith
// Do not move into folder helpers/!
// This folder is also used by compiler/test
// but the testing of the helpers themselves must be performed ONLY HERE.
//
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PathsHelpersTests {
class PathsHelpersTests: FunSpec({
@Nested
inner class AssumeNotExists {
context("AssumeNotExists") {
@Nested
inner class WithOnePathArg {
context("WithOnePathArg") {
@Test
fun `on non-existing path`() {
val path = fixturesDir.div("i_do_not_exist")
assertThat("should return the path",
assumeNotExists(path), `is`(path))
}
@Test
fun `on existing file`() {
assertFailsWith<java.lang.AssertionError> {
assumeNotExists(fixturesDir.div("simple_main.p8"))
test("on non-existing path") {
val path = fixturesDir / "i_do_not_exist"
withClue("should return the path") {
assumeNotExists(path) shouldBe path
}
}
@Test
fun `on existing directory`() {
assertFailsWith<java.lang.AssertionError> {
test("on existing file") {
shouldThrow<java.lang.AssertionError> {
assumeNotExists(fixturesDir / "simple_main.p8")
}
}
test("on existing directory") {
shouldThrow<java.lang.AssertionError> {
assumeNotExists(fixturesDir)
}
}
}
@Nested
inner class WithOneStringArg {
context("WithOneStringArg") {
@Test
fun `on non-existing path`() {
val path = fixturesDir.div("i_do_not_exist")
assertThat("should return the path",
assumeNotExists("$path"), `is`(path))
test("on non-existing path") {
val path = fixturesDir / "i_do_not_exist"
withClue("should return the path") {
assumeNotExists("$path") shouldBe path
}
}
@Test
fun `on existing file`() {
val path = fixturesDir.div("simple_main.p8")
assertFailsWith<java.lang.AssertionError> {
test("on existing file") {
val path = fixturesDir / "simple_main.p8"
shouldThrow<java.lang.AssertionError> {
assumeNotExists("$path")
}
}
@Test
fun `on existing directory`() {
assertFailsWith<java.lang.AssertionError> {
test("on existing directory") {
shouldThrow<java.lang.AssertionError> {
assumeNotExists("$fixturesDir")
}
}
}
@Nested
inner class WithPathAndStringArgs {
context("WithPathAndStringArgs") {
@Test
fun `on non-existing path`() {
val path = fixturesDir.div("i_do_not_exist")
assertThat("should return the path",
assumeNotExists(fixturesDir, "i_do_not_exist"), `is`(path))
test("on non-existing path") {
val path = fixturesDir / "i_do_not_exist"
withClue("should return the path") {
assumeNotExists(fixturesDir / "i_do_not_exist") shouldBe path
}
}
@Test
fun `on existing file`() {
assertFailsWith<java.lang.AssertionError> {
test("on existing file") {
shouldThrow<java.lang.AssertionError> {
assumeNotExists(fixturesDir, "simple_main.p8")
}
}
@Test
fun `on existing directory`() {
assertFailsWith<java.lang.AssertionError> {
test("on existing directory") {
shouldThrow<java.lang.AssertionError> {
assumeNotExists(fixturesDir, "..")
}
}
}
}
@Nested
inner class AssumeDirectory {
context("AssumeDirectory") {
@Nested
inner class WithOnePathArg {
@Test
fun `on non-existing path`() {
val path = fixturesDir.div("i_do_not_exist")
assertFailsWith<AssertionError> {
context("WithOnePathArg") {
test("on non-existing path") {
val path = fixturesDir / "i_do_not_exist"
shouldThrow<AssertionError> {
assumeDirectory(path)
}
}
@Test
fun `on existing file`() {
val path = fixturesDir.div("simple_main.p8")
assertFailsWith<AssertionError> {
test("on existing file") {
val path = fixturesDir / "simple_main.p8"
shouldThrow<AssertionError> {
assumeDirectory(path)
}
}
@Test
fun `on existing directory`() {
test("on existing directory") {
val path = workingDir
assertThat("should return the path", assumeDirectory(path), `is`(path))
withClue("should return the path") {
assumeDirectory(path) shouldBe path
}
}
}
@Nested
inner class WithOneStringArg {
@Test
fun `on non-existing path`() {
val path = fixturesDir.div("i_do_not_exist")
assertFailsWith<AssertionError> {
context("WithOneStringArg") {
test("on non-existing path") {
val path = fixturesDir / "i_do_not_exist"
shouldThrow<AssertionError> {
assumeDirectory("$path")
}
}
@Test
fun `on existing file`() {
val path = fixturesDir.div("simple_main.p8")
assertFailsWith<AssertionError> {
test("on existing file") {
val path = fixturesDir / "simple_main.p8"
shouldThrow<AssertionError> {
assumeDirectory("$path")
}
}
@Test
fun `on existing directory`() {
test("on existing directory") {
val path = workingDir
assertThat("should return the path",
assumeDirectory("$path"), `is`(path))
withClue("should return the path") {
assumeDirectory("$path") shouldBe path
}
}
}
@Nested
inner class WithPathAndStringArgs {
@Test
fun `on non-existing path`() {
assertFailsWith<AssertionError> {
context("WithPathAndStringArgs") {
test("on non-existing path") {
shouldThrow<AssertionError> {
assumeDirectory(fixturesDir, "i_do_not_exist")
}
}
@Test
fun `on existing file`() {
assertFailsWith<AssertionError> {
test("on existing file") {
shouldThrow<AssertionError> {
assumeDirectory(fixturesDir, "simple_main.p8")
}
}
@Test
fun `on existing directory`() {
val path = workingDir.div("..")
assertThat(
"should return resulting path",
assumeDirectory(workingDir, ".."), `is`(path)
)
test("on existing directory") {
val path = workingDir / ".."
withClue("should return resulting path") {
assumeDirectory(workingDir / "..") shouldBe path
}
}
}
@Nested
inner class WithStringAndStringArgs {
@Test
fun `on non-existing path`() {
assertFailsWith<AssertionError> {
context("WithStringAndStringArgs") {
test("on non-existing path") {
shouldThrow<AssertionError> {
assumeDirectory("$fixturesDir", "i_do_not_exist")
}
}
@Test
fun `on existing file`() {
assertFailsWith<AssertionError> {
test("on existing file") {
shouldThrow<AssertionError> {
assumeDirectory("$fixturesDir", "simple_main.p8")
}
}
@Test
fun `on existing directory`() {
val path = workingDir.div("..")
assertThat(
"should return resulting path",
assumeDirectory("$workingDir", ".."), `is`(path)
)
test("on existing directory") {
val path = workingDir / ".."
withClue("should return resulting path") {
assumeDirectory(workingDir / "..") shouldBe path
}
}
}
@Nested
inner class WithStringAndPathArgs {
@Test
fun `on non-existing path`() {
assertFailsWith<AssertionError> {
context("WithStringAndPathArgs") {
test("on non-existing path") {
shouldThrow<AssertionError> {
assumeDirectory("$fixturesDir", Path("i_do_not_exist"))
}
}
@Test
fun `on existing file`() {
assertFailsWith<AssertionError> {
test("on existing file") {
shouldThrow<AssertionError> {
assumeDirectory("$fixturesDir", Path("simple_main.p8"))
}
}
@Test
fun `on existing directory`() {
val path = workingDir.div("..")
assertThat(
"should return resulting path",
assumeDirectory("$workingDir", Path("..")), `is`(path)
)
test("on existing directory") {
val path = workingDir / ".."
withClue("should return resulting path") {
assumeDirectory(workingDir / Path("..")) shouldBe path
}
}
}
}
@Nested
inner class AssumeReadableFile {
context("AssumeReadableFile") {
@Nested
inner class WithOnePathArg {
context("WithOnePathArg") {
@Test
fun `on non-existing path`() {
val path = fixturesDir.div("i_do_not_exist")
assertFailsWith<AssertionError> {
test("on non-existing path") {
val path = fixturesDir / "i_do_not_exist"
shouldThrow<AssertionError> {
assumeReadableFile(path)
}
}
@Test
fun `on readable file`() {
val path = fixturesDir.div("simple_main.p8")
assertThat("should return the path",
assumeReadableFile(path), `is`(path))
test("on readable file") {
val path = fixturesDir / "simple_main.p8"
withClue("should return the path") {
assumeReadableFile(path) shouldBe path
}
}
@Test
fun `on directory`() {
assertFailsWith<AssertionError> {
test("on directory") {
shouldThrow<AssertionError> {
assumeReadableFile(fixturesDir)
}
}
}
@Nested
inner class WithOneStringArg {
context("WithOneStringArg") {
@Test
fun `on non-existing path`() {
val path = fixturesDir.div("i_do_not_exist")
assertFailsWith<AssertionError> {
test("on non-existing path") {
val path = fixturesDir / "i_do_not_exist"
shouldThrow<AssertionError> {
assumeReadableFile("$path")
}
}
@Test
fun `on readable file`() {
val path = fixturesDir.div("simple_main.p8")
assertThat("should return the resulting path",
assumeReadableFile("$path"), `is`(path))
test("on readable file") {
val path = fixturesDir / "simple_main.p8"
withClue("should return the resulting path") {
assumeReadableFile("$path") shouldBe path
}
}
@Test
fun `on directory`() {
assertFailsWith<AssertionError> {
test("on directory") {
shouldThrow<AssertionError> {
assumeReadableFile("$fixturesDir")
}
}
}
@Nested
inner class WithPathAndStringArgs {
@Test
fun `on non-existing path`() {
assertFailsWith<java.lang.AssertionError> {
context("WithPathAndStringArgs") {
test("on non-existing path") {
shouldThrow<java.lang.AssertionError> {
assumeReadableFile(fixturesDir, "i_do_not_exist")
}
}
@Test
fun `on readable file`() {
val path = fixturesDir.div("simple_main.p8")
assertThat("should return the resulting path",
assumeReadableFile(fixturesDir, "simple_main.p8"), `is`(path))
test("on readable file") {
val path = fixturesDir / "simple_main.p8"
withClue("should return the resulting path") {
assumeReadableFile(fixturesDir / "simple_main.p8") shouldBe path
}
}
@Test
fun `on directory`() {
assertFailsWith<AssertionError> {
test("on directory") {
shouldThrow<AssertionError> {
assumeReadableFile(fixturesDir, "..")
}
}
}
@Nested
inner class WithPathAndPathArgs {
@Test
fun `on non-existing path`() {
assertFailsWith<java.lang.AssertionError> {
context("WithPathAndPathArgs") {
test("on non-existing path") {
shouldThrow<java.lang.AssertionError> {
assumeReadableFile(fixturesDir, Path("i_do_not_exist"))
}
}
@Test fun `on readable file`() {
assertThat("should return the resulting path",
assumeReadableFile(fixturesDir, Path("simple_main.p8")),
`is`(fixturesDir.div("simple_main.p8"))
)
test("on readable file") {
withClue("should return the resulting path") {
assumeReadableFile(fixturesDir / Path("simple_main.p8")) shouldBe fixturesDir / "simple_main.p8"
}
}
@Test
fun `on directory`() {
assertFailsWith<AssertionError> {
test("on directory") {
shouldThrow<AssertionError> {
assumeReadableFile(fixturesDir, Path(".."))
}
}
}
@Nested
inner class WithStringAndStringArgs {
@Test
fun `on non-existing path`() {
assertFailsWith<java.lang.AssertionError> {
context("WithStringAndStringArgs") {
test("on non-existing path") {
shouldThrow<java.lang.AssertionError> {
assumeReadableFile("$fixturesDir", "i_do_not_exist")
}
}
@Test
fun `on readable file`() {
assertThat("should return the resulting path",
assumeReadableFile(fixturesDir.toString(), "simple_main.p8"),
`is`(fixturesDir.div("simple_main.p8"))
)
test("on readable file") {
withClue("should return the resulting path") {
assumeReadableFile(fixturesDir / "simple_main.p8") shouldBe fixturesDir / "simple_main.p8"
}
}
@Test
fun `on directory`() {
assertFailsWith<AssertionError> {
test("on directory") {
shouldThrow<AssertionError> {
assumeReadableFile("$fixturesDir", "..")
}
}
}
}
}
})
+1 -24
View File
@@ -15,12 +15,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 {
@@ -32,23 +26,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!
@@ -11,7 +11,5 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<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.
@@ -1,5 +1,6 @@
package prog8.compilerinterface
import prog8.ast.base.FatalAstException
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
@@ -41,7 +42,7 @@ fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean {
}
ident != null -> {
val program = definingModule.program
val decl = ident.targetVarDecl(program)!!
val decl = ident.targetVarDecl(program) ?: throw FatalAstException("invalid identifier ${ident.nameInSource}")
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
@@ -109,11 +109,19 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("sin8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
FSignature("sinr8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinSinR8 ),
FSignature("sinr8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSinR8u ),
FSignature("sinr16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinSinR16 ),
FSignature("sinr16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinSinR16u ),
FSignature("cos" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
FSignature("cosr8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinCosR8 ),
FSignature("cosr8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCosR8u ),
FSignature("cosr16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinCosR16 ),
FSignature("cosr16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinCosR16u ),
FSignature("tan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
@@ -372,6 +380,24 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSinR8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sinr8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSinR8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sinr8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
@@ -390,6 +416,24 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCosR8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cosr8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCosR8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cosr8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
@@ -408,6 +452,24 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSinR16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sinr16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSinR16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sinr16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
@@ -426,6 +488,24 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCosR16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cosr16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinCosR16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cosr16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
}
@Suppress("UNUSED_PARAMETER")
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
@@ -173,14 +173,18 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
fun unused(decl: VarDecl): Boolean {
// Don't check assembly just for occurrences of variables, if they're not used in prog8 itself, just kill them
return usages(decl).isEmpty()
}
fun usages(decl: VarDecl): List<IdentifierReference> {
if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm)
return false
return emptyList()
if(decl.definingBlock !in usedBlocks)
return false
return emptyList()
val allReferencedVardecls = allIdentifiersAndTargets.filter { it.value is VarDecl }.map { it.value }.toSet()
return decl !in allReferencedVardecls // Don't check assembly just for occurrences of variables, if they're not used in prog8 itself, just kill them
return allIdentifiersAndTargets.filter { decl===it.value }.map{ it.key.first }
}
private fun nameInAssemblyCode(name: String) = allAssemblyNodes.any { it.assembly.contains(name) }
@@ -18,15 +18,15 @@ enum class ZeropageType {
DONTUSE
}
data class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val compTarget: ICompilationTarget
) {
var slowCodegenWarnings = false
var optimize = false
var optimizeFloatExpressions = false
}
class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val compTarget: ICompilationTarget,
// these are set based on command line arguments:
var slowCodegenWarnings: Boolean = false,
var optimize: Boolean = false,
var optimizeFloatExpressions: Boolean = false
)
@@ -51,7 +51,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
}
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
require(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage== ZeropageType.DONTUSE)
throw InternalCompilerException("zero page usage has been disabled")
-5
View File
@@ -18,11 +18,6 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
implementation "com.github.hypfvieh:dbus-java:3.3.1"
implementation "org.slf4j:slf4j-simple:1.7.30"
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'
}
configurations.all {
+2
View File
@@ -0,0 +1,2 @@
sphinx>=4.0
sphinx_rtd_theme==1.0.0
+1 -1
View File
@@ -40,7 +40,7 @@ def setup(app):
# If your documentation needs a minimal Sphinx version, state it here.
#
needs_sphinx = '1.5.3'
needs_sphinx = '4.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+32
View File
@@ -704,6 +704,22 @@ cos16u(x)
cos16(x)
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
cosr8u(x)
Fast 8-bit ubyte cosine of angle 0..179 (each is a 2 degree step), result is in range 0..255
Angles 180..255 will yield a garbage result!
cosr8(x)
Fast 8-bit byte cosine of angle 0..179 (each is a 2 degree step), result is in range -127..127
Angles 180..255 will yield a garbage result!
cosr16u(x)
Fast 16-bit uword cosine of angle 0..179 (each is a 2 degree step), result is in range 0..65535
Angles 180..255 will yield a garbage result!
cosr16(x)
Fast 16-bit word cosine of angle 0..179 (each is a 2 degree step), result is in range -32767..32767
Angles 180..255 will yield a garbage result!
deg(x)
Radians to degrees.
@@ -740,6 +756,22 @@ sin16u(x)
sin16(x)
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
sinr8u(x)
Fast 8-bit ubyte sine of angle 0..179 (each is a 2 degree step), result is in range 0..255
Angles 180..255 will yield a garbage result!
sinr8(x)
Fast 8-bit byte sine of angle 0..179 (each is a 2 degree step), result is in range -127..127
Angles 180..255 will yield a garbage result!
sinr16u(x)
Fast 16-bit uword sine of angle 0..179 (each is a 2 degree step), result is in range 0..65535
Angles 180..255 will yield a garbage result!
sinr16(x)
Fast 16-bit word sine of angle 0..179 (each is a 2 degree step), result is in range -32767..32767
Angles 180..255 will yield a garbage result!
sqrt16(w)
16 bit unsigned integer Square root. Result is unsigned byte.
To do the reverse, squaring an integer, just write ``x*x``.
+4 -2
View File
@@ -609,12 +609,14 @@ flag such as Carry (Pc).
.. note::
The 'virtual' 16-bit registers from the Commander X16 can also be used as ``R0`` .. ``R15`` .
The 'virtual' 16-bit registers from the Commander X16 can also be specified as ``R0`` .. ``R15`` .
This means you don't have to set them up manually before calling a subroutine that takes
one or more parameters in those 'registers'. You can just list the arguments directly.
*This also works on the Commodore-64!* (however they are not as efficient there because they're not in zeropage)
In prog8 and assembly code these 'registers' are directly accessible too via
``cx16.r0`` .. ``cx16.r15`` (they're memory mapped uword values)
``cx16.r0`` .. ``cx16.r15`` (these are memory mapped uword values),
``cx16.r0s`` .. ``cx16.r15s`` (these are memory mapped word values),
and ``L`` / ``H`` variants are also available to directly access the low and high bytes of these.
Expressions
+7 -4
View File
@@ -6,18 +6,21 @@ For next compiler release (7.3)
...
Blocked by Commander-x16 v39 release
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Blocked by an official Commander-x16 v39 release
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- simplify cx16.joystick_get2() once this cx16 rom issue is resolved: https://github.com/commanderx16/x16-rom/issues/203
(I hope this will still be included into the final v39 roms release for the cx16)
Future
^^^^^^
- simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation
- remove special code generation for while and util expression
by rewriting while and until expressions into if+jump (consider them syntactic sugar)
but the result should not produce larger code ofcourse!
- while-expression should now also get the simplifyConditionalExpression() treatment
- fix the asm-labels problem (github issue #62)
- start migrating to KoTest library (github issue #70)
- find a way to optimize asm-subroutine param passing where it now sometimes uses the evalstack?
- [complicated?] find a way to optimize if-statement codegen so that "if var & %10000" doesn't use evalstack & subroutine call, but also that the simple case "if X {...}" remains fast
- document the various compiler command line options in more detail. See "Compiling program code" in the docs
- get rid of all TODO's in the code
- improve testability further, add more tests
+106
View File
@@ -0,0 +1,106 @@
%import graphics
; Note: this program is compatible with CX16 only.
; it doesn't work correctly on C64 because the bitmap screen data overlaps
; the program itself in memory $2000-...
main {
const uword width = 320
const ubyte height = 200
sub start() {
graphics.enable_bitmap_mode()
sincos255()
sys.wait(120)
graphics.clear_screen(1, 0)
sincos180()
sys.wait(120)
graphics.clear_screen(1, 0)
circles()
repeat {
}
}
sub sincos255() {
graphics.line(256,0,256,height-1)
ubyte pixelyb
uword pixelyw
ubyte pixelxb
for pixelxb in 0 to 255 {
pixelyb = cos8u(pixelxb) / 2
graphics.plot(pixelxb, pixelyb)
pixelyb = sin8u(pixelxb) / 2
graphics.plot(pixelxb, pixelyb)
}
for pixelxb in 0 to 255 {
pixelyw = cos16u(pixelxb) / 1024 + 120
graphics.plot(pixelxb, lsb(pixelyw))
pixelyw = sin16u(pixelxb) / 1024 + 120
graphics.plot(pixelxb, lsb(pixelyw))
}
}
sub sincos180() {
graphics.line(180,0,180,height-1)
ubyte pixelyb
uword pixelyw
ubyte pixelxb
for pixelxb in 0 to 179 {
pixelyb = cosr8u(pixelxb) / 2
graphics.plot(pixelxb, pixelyb)
pixelyb = sinr8u(pixelxb) / 2
graphics.plot(pixelxb, pixelyb)
}
for pixelxb in 0 to 179 {
pixelyw = cosr16u(pixelxb) / 1024 + 120
graphics.plot(pixelxb, lsb(pixelyw))
pixelyw = sinr16u(pixelxb) / 1024 + 120
graphics.plot(pixelxb, lsb(pixelyw))
}
}
sub circles() {
ubyte pixelyb
uword pixelxw
ubyte r
; circles with "degrees" from 0 to 255
for r in 0 to 255 {
pixelxw = ((sin8(r)/2 as word) + 80) as uword
pixelyb = (cos8(r)/2 + height/2) as ubyte
graphics.plot(pixelxw, pixelyb)
}
for r in 0 to 255 {
pixelxw = (sin16(r)/1024 + 80) as uword
pixelyb = (cos16(r)/1024 + height/2) as ubyte
graphics.plot(pixelxw, pixelyb)
}
; circles with half-degrees from 0 to 179 (=full degrees 0..358 with steps of 2 degrees)
for r in 0 to 179 {
pixelxw = (sinr8(r) as word /2 + 220) as uword
pixelyb = (cosr8(r)/2 + height/2) as ubyte
graphics.plot(pixelxw, pixelyb)
}
for r in 0 to 179 {
pixelxw = (sinr16(r) as word /1024 + 220) as uword
pixelyb = (cosr16(r)/1024 + height/2) as ubyte
graphics.plot(pixelxw, pixelyb)
}
}
}
+49 -16
View File
@@ -1,31 +1,65 @@
%import textio
; %import floats
%zeropage basicsafe
main {
sub start() {
word xx=0
if not xx {
txt.print("xx is zero\n")
sub start() {
ubyte xx=10
txt.print("if\n")
if xx-2 {
txt.print("in if\n")
}
txt.print("\nwhile\n")
xx=6
while xx-2 {
xx--
txt.print_ub(xx)
txt.spc()
}
txt.print("\nuntil\n")
xx=2
do {
xx++
txt.print_ub(xx)
txt.spc()
} until xx+2==10
txt.print("\ndone")
repeat {
}
}
while not xx {
xx ++
}
do {
xx--
} until not xx
; if not iteration_in_progress or not num_bytes
; return
if not xx {
txt.print("xx is zero\n")
}
; word xx=0
; word[] xarr = [1,2,3]
; ubyte ai
;
; if not @($c000) {
; txt.print("xx is zero\n")
; }
;
; while not xarr[ai] {
; xx ++
; }
;
; do {
; xx--
; } until not xarr[ai]
;
; if not xarr[ai] {
; txt.print("xx is zero\n")
; }
; ubyte yy=$30
; ubyte zz=9
; sys.memset(xx+200, yy*2, ~yy)
;
;
; if yy & %10000 {
; yy++
; }
@@ -43,5 +77,4 @@ main {
; txt.nl()
; floats.print_f(fa[1])
}
}
-5
View File
@@ -22,11 +22,6 @@ dependencies {
implementation "javax.json:javax.json-api:1.1.4"
implementation "org.glassfish:javax.json:1.1.4"
implementation "org.slf4j:slf4j-simple:1.7.30"
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'
}
configurations.all {
+1 -1
View File
@@ -182,10 +182,10 @@ expression :
| left = expression EOL? bop = '|' EOL? right = expression
| left = expression EOL? bop = ('==' | '!=') EOL? right = expression
| rangefrom = expression rto = ('to'|'downto') rangeto = expression ('step' rangestep = expression)? // can't create separate rule due to mutual left-recursion
| prefix = 'not' expression
| left = expression EOL? bop = 'and' EOL? right = expression
| left = expression EOL? bop = 'or' EOL? right = expression
| left = expression EOL? bop = 'xor' EOL? right = expression
| prefix = 'not' expression
| literalvalue
| scoped_identifier
| arrayindexed
+1 -1
View File
@@ -2,5 +2,5 @@
rm -f *.jar *.asm *.prg *.vm.txt *.vice-mon-list a.out
rm -rf build out
rm -rf compiler/build compilerAst/build dbusCompilerService/build httpCompilerService/build parser/build
rm -rf compiler/build codeGeneration/build codeOptimizers/build compilerInterfaces/build compilerAst/build dbusCompilerService/build httpCompilerService/build parser/build