Merge branch 'v7.2'

# Conflicts:
#	compiler/res/version.txt
This commit is contained in:
Irmen de Jong 2021-11-06 23:41:39 +01:00
commit 381cfca67f
132 changed files with 3442 additions and 1865 deletions

6
.idea/kotlinc.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="11" />
</component>
</project>

View File

@ -1,22 +1,22 @@
<component name="libraryTable">
<library name="github.hypfvieh.dbus.java" type="repository">
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.0" />
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.0/dbus-java-3.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.5/jnr-unixsocket-0.38.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.1/jnr-ffi-2.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.1/dbus-java-3.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.6/jnr-unixsocket-0.38.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.2/jnr-ffi-2.2.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.0/asm-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.0/asm-commons-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.0/asm-analysis-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.0/asm-tree-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.0/asm-util-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.1/asm-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.1/asm-commons-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.1/asm-analysis-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.1/asm-tree-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.1/asm-util-9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.3/jnr-enxio-0.32.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.4/jnr-posix-3.1.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.4/jnr-enxio-0.32.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.5/jnr-posix-3.1.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
</CLASSES>
<JAVADOC />

3
.idea/modules.xml generated
View File

@ -2,8 +2,11 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/codeGeneration/codeGeneration.iml" filepath="$PROJECT_DIR$/codeGeneration/codeGeneration.iml" />
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" filepath="$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" />
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />

View File

@ -1,3 +1,10 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.5.30" apply false
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}

View File

@ -0,0 +1,56 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
dependencies {
implementation project(':compilerInterfaces')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// 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'
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
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"
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<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" />
</component>
</module>

View File

@ -0,0 +1,3 @@
package prog8.compiler.target
class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -0,0 +1,35 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compilerinterface.ICompilationTarget
object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}

View File

@ -0,0 +1,36 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}

View File

@ -1,16 +1,13 @@
package prog8.compiler.target.c64
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
import kotlin.math.absoluteValue
import kotlin.math.pow
internal object C64MachineDefinition: IMachineDefinition {
object C64MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
@ -30,7 +27,7 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
@ -77,7 +74,7 @@ internal object C64MachineDefinition: IMachineDefinition {
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
@ -87,12 +84,11 @@ internal object C64MachineDefinition: IMachineDefinition {
init {
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
@ -125,7 +121,7 @@ internal object C64MachineDefinition: IMachineDefinition {
))
}
if(options.zeropage!=ZeropageType.DONTUSE) {
if(options.zeropage!= ZeropageType.DONTUSE) {
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
@ -136,17 +132,13 @@ internal object C64MachineDefinition: IMachineDefinition {
free.clear()
}
}
require(SCRATCH_B1 !in free)
require(SCRATCH_REG !in free)
require(SCRATCH_W1 !in free)
require(SCRATCH_W2 !in free)
for (reserved in options.zpReserved)
reserve(reserved)
removeReservedFromFreePool()
}
}
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat {
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short):
IMachineFloat {
companion object {
val zero = Mflpt5(0, 0, 0, 0, 0)
@ -157,7 +149,7 @@ internal object C64MachineDefinition: IMachineDefinition {
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0)
return zero
@ -178,7 +170,7 @@ internal object C64MachineDefinition: IMachineDefinition {
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw CompilerException("floating point overflow: $this")
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()

View File

@ -1,10 +1,17 @@
package prog8.compiler.target.cbm
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IAssemblyProgram
import prog8.compilerinterface.OutputType
import prog8.compilerinterface.generatedLabelPrefix
import prog8.parser.SourceCode
import java.io.File
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
internal const val viceMonListPostfix = "vice-mon-list"
@ -20,12 +27,15 @@ class AssemblyProgram(
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
override fun assemble(options: CompilationOptions): Int {
override fun assemble(quiet: Boolean, options: CompilationOptions): Int {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
if(quiet)
command.add("--quiet")
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
@ -76,3 +86,18 @@ class AssemblyProgram(
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
}
}
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
return com.github.michaelbull.result.runCatching {
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").readText()
}.mapError { NoSuchFileException(File(filename)) }
} else {
val sib = Path(source.origin).resolveSibling(filename)
if (sib.isRegularFile())
Ok(SourceCode.File(sib).readText())
else
Ok(SourceCode.File(Path(filename)).readText())
}
}

View File

@ -11,7 +11,7 @@ object Petscii {
// decoding: from Petscii/Screencodes (0-255) to unicode
// character tables used from https://github.com/dj51d/cbmcodecs
private val decodingPetsciiLowercase = arrayOf(
private val decodingPetsciiLowercase = charArrayOf(
'\u0000', // 0x00 -> \u0000
'\ufffe', // 0x01 -> UNDEFINED
'\ufffe', // 0x02 -> UNDEFINED
@ -270,7 +270,7 @@ object Petscii {
'\u2592' // ▒ 0xFF -> MEDIUM SHADE
)
private val decodingPetsciiUppercase = arrayOf(
private val decodingPetsciiUppercase = charArrayOf(
'\u0000', // 0x00 -> \u0000
'\ufffe', // 0x01 -> UNDEFINED
'\ufffe', // 0x02 -> UNDEFINED
@ -369,13 +369,13 @@ object Petscii {
'\u2190', // ← 0x5F -> LEFTWARDS ARROW
'\u2500', // ─ 0x60 -> BOX DRAWINGS LIGHT HORIZONTAL
'\u2660', // ♠ 0x61 -> BLACK SPADE SUIT
'\u2502', // │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
'\u2500', // ─ 0x63 -> BOX DRAWINGS LIGHT HORIZONTAL
'\uf122', //  0x64 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP (CUS)
'\uf123', //  0x65 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS UP (CUS)
'\uf124', //  0x66 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER DOWN (CUS)
'\uf126', //  0x67 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER LEFT (CUS)
'\uf128', //  0x68 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER RIGHT (CUS)
'\uf13c', // │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)
'\uf13b', // ─ 0x63 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)
'\uf122', //  0x64 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS UP (CUS)
'\uf123', //  0x65 -> BOX DRAWINGS LIGHT HORIZONTAL THREE EIGHTHS UP (CUS)
'\uf124', //  0x66 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH DOWN (CUS)
'\uf126', //  0x67 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTHS LEFT (CUS)
'\uf128', //  0x68 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH RIGHT (CUS)
'\u256e', // ╮ 0x69 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
'\u2570', // ╰ 0x6A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
'\u256f', // ╯ 0x6B -> BOX DRAWINGS LIGHT ARC UP AND LEFT
@ -385,14 +385,14 @@ object Petscii {
'\uf12b', //  0x6F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
'\uf12c', //  0x70 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
'\u25cf', // ● 0x71 -> BLACK CIRCLE
'\uf125', //  0x72 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS DOWN (CUS)
'\uf125', //  0x72 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS DOWN (CUS)
'\u2665', // ♥ 0x73 -> BLACK HEART SUIT
'\uf127', //  0x74 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS LEFT (CUS)
'\uf127', //  0x74 -> BOX DRAWINGS LIGHT VERTICAL THREE EIGHTHS LEFT (CUS)
'\u256d', // ╭ 0x75 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
'\u2573', // 0x76 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
'\u25cb', // ○ 0x77 -> WHITE CIRCLE
'\u2663', // ♣ 0x78 -> BLACK CLUB SUIT
'\uf129', //  0x79 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS RIGHT (CUS)
'\uf129', //  0x79 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTS RIGHT (CUS)
'\u2666', // ♦ 0x7A -> BLACK DIAMOND SUIT
'\u253c', // ┼ 0x7B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
'\uf12e', //  0x7C -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
@ -465,13 +465,13 @@ object Petscii {
'\u259a', // ▚ 0xBF -> QUADRANT UPPER LEFT AND LOWER RIGHT
'\u2500', // ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
'\u2660', // ♠ 0xC1 -> BLACK SPADE SUIT
'\u2502', // │ 0xC2 -> BOX DRAWINGS LIGHT VERTICAL
'\u2500', // ─ 0xC3 -> BOX DRAWINGS LIGHT HORIZONTAL
'\uf122', //  0xC4 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP (CUS)
'\uf123', //  0xC5 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS UP (CUS)
'\uf124', //  0xC6 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER DOWN (CUS)
'\uf126', //  0xC7 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER LEFT (CUS)
'\uf128', //  0xC8 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER RIGHT (CUS)
'\uf13c', // │ 0xC2 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)
'\uf13b', // ─ 0xC3 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)
'\uf122', //  0xC4 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS UP (CUS)
'\uf123', //  0xC5 -> BOX DRAWINGS LIGHT HORIZONTAL THREE EIGHTHS UP (CUS)
'\uf124', //  0xC6 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH DOWN (CUS)
'\uf126', //  0xC7 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTHS LEFT (CUS)
'\uf128', //  0xC8 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH RIGHT (CUS)
'\u256e', // ╮ 0xC9 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
'\u2570', // ╰ 0xCA -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
'\u256f', // ╯ 0xCB -> BOX DRAWINGS LIGHT ARC UP AND LEFT
@ -481,14 +481,14 @@ object Petscii {
'\uf12b', //  0xCF -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
'\uf12c', //  0xD0 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
'\u25cf', // ● 0xD1 -> BLACK CIRCLE
'\uf125', //  0xD2 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS DOWN (CUS)
'\uf125', //  0xD2 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTS DOWN (CUS)
'\u2665', // ♥ 0xD3 -> BLACK HEART SUIT
'\uf127', //  0xD4 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS LEFT (CUS)
'\uf127', //  0xD4 -> BOX DRAWINGS LIGHT VERTICAL THREE EIGHTS LEFT (CUS)
'\u256d', // ╭ 0xD5 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
'\u2573', // 0xD6 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
'\u25cb', // ○ 0xD7 -> WHITE CIRCLE
'\u2663', // ♣ 0xD8 -> BLACK CLUB SUIT
'\uf129', //  0xD9 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS RIGHT (CUS)
'\uf129', //  0xD9 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTS RIGHT (CUS)
'\u2666', // ♦ 0xDA -> BLACK DIAMOND SUIT
'\u253c', // ┼ 0xDB -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
'\uf12e', //  0xDC -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
@ -529,7 +529,7 @@ object Petscii {
'\u03c0' // π 0xFF -> GREEK SMALL LETTER PI
)
private val decodingScreencodeLowercase = arrayOf(
private val decodingScreencodeLowercase = charArrayOf(
'@' , // @ 0x00 -> COMMERCIAL AT
'a' , // a 0x01 -> LATIN SMALL LETTER A
'b' , // b 0x02 -> LATIN SMALL LETTER B
@ -788,7 +788,7 @@ object Petscii {
'\ufffe' // 0xFF -> UNDEFINED
)
private val decodingScreencodeUppercase = arrayOf(
private val decodingScreencodeUppercase = charArrayOf(
'@' , // @ 0x00 -> COMMERCIAL AT
'A' , // A 0x01 -> LATIN CAPITAL LETTER A
'B' , // B 0x02 -> LATIN CAPITAL LETTER B
@ -855,13 +855,13 @@ object Petscii {
'?' , // ? 0x3F -> QUESTION MARK
'\u2500', // ─ 0x40 -> BOX DRAWINGS LIGHT HORIZONTAL
'\u2660', // ♠ 0x41 -> BLACK SPADE SUIT
'\u2502', // │ 0x42 -> BOX DRAWINGS LIGHT VERTICAL
'\u2500', // ─ 0x43 -> BOX DRAWINGS LIGHT HORIZONTAL
'\uf122', //  0x44 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP (CUS)
'\uf123', //  0x45 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS UP (CUS)
'\uf124', //  0x46 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER DOWN (CUS)
'\uf126', //  0x47 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER LEFT (CUS)
'\uf128', //  0x48 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER RIGHT (CUS)
'\uf13c', // │ 0x42 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)
'\uf13b', // ─ 0x43 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)
'\uf122', //  0x44 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS UP (CUS)
'\uf123', //  0x45 -> BOX DRAWINGS LIGHT HORIZONTAL THREE EIGHTHS UP (CUS
'\uf124', //  0x46 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH DOWN (CUS)
'\uf126', //  0x47 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTHS LEFT (CUS)
'\uf128', //  0x48 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH RIGHT (CUS)
'\u256e', // ╮ 0x49 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
'\u2570', // ╰ 0x4A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
'\u256f', // ╯ 0x4B -> BOX DRAWINGS LIGHT ARC UP AND LEFT
@ -871,14 +871,14 @@ object Petscii {
'\uf12b', //  0x4F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
'\uf12c', //  0x50 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
'\u25cf', // ● 0x51 -> BLACK CIRCLE
'\uf125', //  0x52 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS DOWN (CUS)
'\uf125', //  0x52 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTS DOWN (CUS)
'\u2665', // ♥ 0x53 -> BLACK HEART SUIT
'\uf127', //  0x54 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS LEFT (CUS)
'\uf127', //  0x54 -> BOX DRAWINGS LIGHT VERTICAL THREE EIGHTS LEFT (CUS)
'\u256d', // ╭ 0x55 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
'\u2573', // 0x56 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
'\u25cb', // ○ 0x57 -> WHITE CIRCLE
'\u2663', // ♣ 0x58 -> BLACK CLUB SUIT
'\uf129', //  0x59 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS RIGHT (CUS)
'\uf129', //  0x59 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTS RIGHT (CUS)
'\u2666', // ♦ 0x5A -> BLACK DIAMOND SUIT
'\u253c', // ┼ 0x5B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
'\uf12e', //  0x5C -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
@ -1098,12 +1098,9 @@ object Petscii {
fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String {
return petscii.map {
val code = it.toInt()
try {
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
} catch(x: CharConversionException) {
// TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString?
if(lowercase) decodingPetsciiUppercase[code] else decodingPetsciiLowercase[code]
}
if(code<0 || code>= decodingPetsciiLowercase.size)
throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
}.joinToString("")
}
@ -1140,17 +1137,15 @@ object Petscii {
fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String {
return screencode.map {
val code = it.toInt()
try {
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
} catch (x: CharConversionException) {
// TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString?
if (lowercase) decodingScreencodeUppercase[code] else decodingScreencodeLowercase[code]
}
if(code<0 || code>= decodingScreencodeLowercase.size)
throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
}.joinToString("")
}
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Result<Short, CharConversionException> {
val code = when {
petscii_code < 0 -> return Err(CharConversionException("petscii code out of range"))
petscii_code <= 0x1f -> petscii_code + 128
petscii_code <= 0x3f -> petscii_code.toInt()
petscii_code <= 0x5f -> petscii_code - 64
@ -1168,6 +1163,7 @@ object Petscii {
fun scr2petscii(screencode: Short): Result<Short, CharConversionException> {
val petscii = when {
screencode < 0 -> return Err(CharConversionException("screencode out of range"))
screencode <= 0x1f -> screencode + 64
screencode <= 0x3f -> screencode.toInt()
screencode <= 0x5d -> screencode +123

View File

@ -1,30 +1,32 @@
package prog8.compiler.target.cpu6502.codegen
import com.github.michaelbull.result.*
import com.github.michaelbull.result.fold
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.FSignature
import prog8.compiler.target.*
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cbm.AssemblyProgram
import prog8.compiler.target.cbm.loadAsmIncludeFile
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
import prog8.optimizer.CallGraph
import prog8.compiler.target.cpu6502.codegen.assignment.SourceStorageKind
import prog8.compilerinterface.*
import prog8.parser.SourceCode
import java.nio.file.Path
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program,
class AsmGen(private val program: Program,
val errors: IErrorReporter,
val zeropage: Zeropage,
val options: CompilationOptions,
@ -47,9 +49,9 @@ internal class AsmGen(private val program: Program,
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
internal val loopEndLabels = ArrayDeque<String>()
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
internal val slabs = mutableMapOf<String, Int>()
internal val removals = mutableListOf<Pair<Statement, INameScope>>()
internal val removals = mutableListOf<Pair<Statement, IStatementContainer>>()
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
override fun compileToAssembly(): IAssemblyProgram {
assemblyLines.clear()
@ -61,8 +63,11 @@ internal class AsmGen(private val program: Program,
val allBlocks = program.allBlocks
if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'")
for(b in program.allBlocks)
for(b in program.allBlocks) {
b.statements.removeAll(blockVariableInitializers.getValue(b)) // no longer output the initialization assignments as regular statements in the block!
block2asm(b)
}
for(removal in removals.toList()) {
removal.second.remove(removal.first)
@ -219,26 +224,17 @@ internal class AsmGen(private val program: Program,
stmts.forEach { translate(it) }
subroutine.forEach { translateSubroutine(it as Subroutine) }
// if any global vars need to be initialized, generate a subroutine that does this
// it will be called from program init.
if(block in blockLevelVarInits) {
// generate subroutine to initialize block-level variables
val initializers = blockVariableInitializers.getValue(block)
if(initializers.isNotEmpty()) {
out("prog8_init_vars\t.proc\n")
blockLevelVarInits.getValue(block).forEach { decl ->
val scopedFullName = decl.makeScopedName(decl.name).split('.')
require(scopedFullName.first()==block.name)
assignInitialValueToVar(decl, scopedFullName.drop(1))
}
initializers.forEach { assign -> translate(assign) }
out(" rts\n .pend")
}
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
}
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
val asmName = asmVariableName(variableName)
assignmentAsmGen.assignExpressionToVariable(decl.value!!, asmName, decl.datatype, decl.definingSubroutine)
}
private var generatedLabelSequenceNumber: Int = 0
internal fun makeLabel(postfix: String): String {
@ -275,7 +271,6 @@ internal class AsmGen(private val program: Program,
&& variable.datatype != DataType.FLOAT
&& options.zeropage != ZeropageType.DONTUSE) {
try {
val errors = ErrorReporter()
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
errors.report()
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
@ -500,7 +495,7 @@ internal class AsmGen(private val program: Program,
return newName
}
internal fun asmSymbolName(identifier: IdentifierReference): String {
fun asmSymbolName(identifier: IdentifierReference): String {
if(identifier.nameInSource.size==2 && identifier.nameInSource[0]=="prog8_slabs")
return identifier.nameInSource.joinToString(".")
@ -531,7 +526,7 @@ internal class AsmGen(private val program: Program,
}
}
internal fun asmVariableName(identifier: IdentifierReference) =
fun asmVariableName(identifier: IdentifierReference) =
fixNameSymbols(identifier.nameInSource.joinToString("."))
private fun getScopedSymbolNameForTarget(actualName: String, target: Statement): MutableList<String> {
@ -837,12 +832,10 @@ internal class AsmGen(private val program: Program,
}
}
@Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateExpression(indexer: ArrayIndex) =
expressionsAsmGen.translateExpression(indexer)
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack, resultRegister)
@ -858,14 +851,53 @@ internal class AsmGen(private val program: Program,
internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign)
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) =
assignmentAsmGen.assignExpressionToRegister(expr, register)
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean=false) =
assignmentAsmGen.assignExpressionToRegister(expr, register, signed)
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) =
assignmentAsmGen.assignExpressionToVariable(expr, asmVarName, dt, scope)
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) =
assignmentAsmGen.assignVariableToRegister(asmVarName, register)
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean=false) =
assignmentAsmGen.assignVariableToRegister(asmVarName, register, signed)
internal fun assignRegister(reg: RegisterOrPair, target: AsmAssignTarget) {
when(reg) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister())
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> assignmentAsmGen.assignRegisterpairWord(target, reg)
RegisterOrPair.FAC1 -> assignmentAsmGen.assignFAC1float(target)
RegisterOrPair.FAC2 -> TODO("no support yet to assign FAC2 directly to something")
else -> throw AssemblyError("invalid register")
}
}
internal fun assignExpressionTo(value: Expression, target: AsmAssignTarget) {
// don't use asmgen.translateExpression() to avoid evalstack
when (target.datatype) {
in ByteDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.A)
assignRegister(RegisterOrPair.A, target)
}
in WordDatatypes, in PassByReferenceDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.AY)
translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.REGISTER, program, this, target.datatype, register=RegisterOrPair.AY),
target, false, program.memsizer, value.position
)
)
}
DataType.FLOAT -> {
assignExpressionToRegister(value, RegisterOrPair.FAC1)
assignRegister(RegisterOrPair.FAC1, target)
}
else -> throw AssemblyError("weird dt ${target.datatype}")
}
}
private fun translateSubroutine(sub: Subroutine) {
@ -909,9 +941,9 @@ internal class AsmGen(private val program: Program,
if(sub.name=="start" && sub.definingBlock.name=="main") {
out("; program startup initialization")
out(" cld")
program.allBlocks.forEach {
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
out(" jsr ${it.name}.prog8_init_vars")
blockVariableInitializers.forEach {
if(it.value.isNotEmpty())
out(" jsr ${it.key.name}.prog8_init_vars")
}
out("""
tsx
@ -984,15 +1016,14 @@ internal class AsmGen(private val program: Program,
}
private fun translate(stmt: IfStatement) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
requireComparisonExpression(stmt.condition) // IfStatement: condition must be of form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
// DISABLED FOR NOW:
// if(!booleanCondition.left.isSimple || !booleanCondition.right.isSimple)
// throw AssemblyError("both operands for if comparison expression should have been simplified")
if (stmt.elsepart.containsNoCodeNorVars) {
// empty else
if (stmt.elsepart.isEmpty()) {
val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.truepart)
@ -1011,9 +1042,9 @@ internal class AsmGen(private val program: Program,
}
}
private fun checkBooleanExpression(condition: Expression) {
private fun requireComparisonExpression(condition: Expression) {
if(condition !is BinaryExpression || condition.operator !in comparisonOperators)
throw AssemblyError("expected boolean expression $condition")
throw AssemblyError("expected boolean comparison expression $condition")
}
private fun translate(stmt: RepeatLoop) {
@ -1164,7 +1195,7 @@ $repeatLabel lda $counterVar
}
private fun translate(stmt: WhileLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
requireComparisonExpression(stmt.condition) // WhileLoop: condition must be of form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
@ -1178,7 +1209,7 @@ $repeatLabel lda $counterVar
}
private fun translate(stmt: UntilLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
requireComparisonExpression(stmt.condition) // UntilLoop: condition must be of form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
@ -1245,7 +1276,7 @@ $repeatLabel lda $counterVar
}
private fun translate(stmt: BranchStatement) {
if(stmt.truepart.containsNoCodeNorVars && stmt.elsepart.containsCodeOrVars)
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
throw AssemblyError("only else part contains code, shoud have been switched already")
val jump = stmt.truepart.statements.first() as? Jump
@ -1257,7 +1288,7 @@ $repeatLabel lda $counterVar
} else {
val truePartIsJustBreak = stmt.truepart.statements.firstOrNull() is Break
val elsePartIsJustBreak = stmt.elsepart.statements.firstOrNull() is Break
if(stmt.elsepart.containsNoCodeNorVars) {
if(stmt.elsepart.isEmpty()) {
if(truePartIsJustBreak) {
// branch with just a break (jump out of loop)
val instruction = branchInstruction(stmt.condition, false)
@ -1297,34 +1328,15 @@ $repeatLabel lda $counterVar
}
}
private fun translate(stmt: VarDecl) {
if(stmt.value!=null && stmt.type==VarDeclType.VAR && stmt.datatype in NumericDatatypes) {
// generate an assignment statement to (re)initialize the variable's value.
// if the vardecl is not in a subroutine however, we have to initialize it globally.
if(stmt.definingSubroutine ==null) {
val block = stmt.definingBlock
var inits = blockLevelVarInits[block]
if(inits==null) {
inits = mutableSetOf()
blockLevelVarInits[block] = inits
}
inits.add(stmt)
} else {
val next = (stmt.parent as INameScope).nextSibling(stmt)
if (next !is ForLoop || next.loopVar.nameInSource.single() != stmt.name) {
assignInitialValueToVar(stmt, listOf(stmt.name))
}
}
}
private fun translate(decl: VarDecl) {
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw AssemblyError("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
// at this time, nothing has to be done here anymore code-wise
}
/**
* TODO: %asminclude and %asmbinary should be done earlier than code gen (-> put content into AST) ... (describe why?)
*/
private fun translate(stmt: Directive) {
when(stmt.directive) {
"%asminclude" -> {
// TODO: handle %asminclude with SourceCode
val includedName = stmt.args[0].str!!
if(stmt.definingModule.source is SourceCode.Generated)
TODO("%asminclude inside non-library, non-filesystem module")
@ -1387,7 +1399,7 @@ $label nop""")
else -> {
// all else take its address and assign that also to AY register pair
val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position)
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!)
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!, false)
}
}
}

View File

@ -180,7 +180,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
// 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()

View File

@ -10,12 +10,12 @@ import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.functions.FSignature
import prog8.compiler.target.CpuType
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cpu6502.codegen.assignment.*
import prog8.compiler.target.subroutineFloatEvalResultVar2
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.FSignature
import prog8.compilerinterface.subroutineFloatEvalResultVar2
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
@ -261,7 +261,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(resultToStack)
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
else
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, null, program, asmgen)
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign)
asmgen.slabs[name] = size
@ -273,7 +273,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
else {
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
@ -285,11 +285,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when(func.name) {
"sin8", "sin8u", "cos8", "cos8u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
"sin16", "sin16u", "cos16", "cos16u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
}
}
@ -578,7 +578,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr floats.func_${func.name}_stack")
else {
asmgen.out(" jsr floats.func_${func.name}_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
}
@ -603,7 +603,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
@ -624,7 +624,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
@ -644,23 +644,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
@ -683,23 +683,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_sum_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
@ -785,7 +785,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
) {
val pointerVar = firstExpr.left as IdentifierReference
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
if(firstOffset!=secondOffset) {
swapArrayValues(
@ -876,21 +875,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.translateNormalAssignment(assignSecond)
}
DataType.FLOAT -> {
// via evaluation stack
asmgen.translateExpression(first)
asmgen.translateExpression(second)
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
// via temp variable and FAC1
asmgen.assignExpressionTo(first, AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.FLOAT, first.definingSubroutine, "floats.tempvar_swap_float"))
asmgen.assignExpressionTo(second, AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, null, register=RegisterOrPair.FAC1))
asmgen.translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, datatype, register = RegisterOrPair.FAC1),
targetFromExpr(first, datatype),
false, program.memsizer, first.position
)
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
asmgen.translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, "floats.tempvar_swap_float"),
targetFromExpr(second, datatype),
false, program.memsizer, second.position
)
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
}
else -> throw AssemblyError("weird swap dt")
}
@ -1140,15 +1141,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt) {
in ByteDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
in WordDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.FLOAT -> {
asmgen.out(" jsr floats.abs_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type")
}
@ -1162,7 +1163,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_rnd_stack")
else {
asmgen.out(" jsr math.randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
"rndw" -> {
@ -1170,7 +1171,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_rndw_stack")
else {
asmgen.out(" jsr math.randword")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
}
else -> throw AssemblyError("wrong func")
@ -1533,7 +1534,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
AsmAssignSource.fromAstSource(value, program, asmgen)
}
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
}

View File

@ -3,19 +3,27 @@ package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.target.CpuType
import prog8.compiler.target.subroutineFloatEvalResultVar1
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.subroutineFloatEvalResultVar1
import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateExpression(expression: Expression) {
@Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression:Expression) {
if (this.asmgen.options.slowCodegenWarnings) {
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
}
translateExpressionInternal(expression)
}
private fun translateExpressionInternal(expression: Expression) {
when(expression) {
is PrefixExpression -> translateExpression(expression)
is BinaryExpression -> translateExpression(expression)
@ -28,17 +36,21 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is FunctionCall -> translateFunctionCallResultOntoStack(expression)
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
}
}
internal fun translateComparisonExpressionWithJumpIfFalse(expr: BinaryExpression, jumpIfFalseLabel: String) {
// This is a helper routine called from while, do-util, and if expressions to generate optimized conditional branching code.
// This is a helper routine called from while, do-util, and if expressions to generate optimized conditional branching code.
// First, if it is of the form: <constvalue> <comparison> X , then flip the expression so the constant is always the right operand.
var left = expr.left
var right = expr.right
var operator = expr.operator
var leftConstVal = left.constValue(program)
var rightConstVal = right.constValue(program)
// make sure the constant value is on the right of the comparison expression
if(leftConstVal!=null) {
val tmp = left
left = right
@ -54,32 +66,113 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
val idt = left.inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.getOr(DataType.UNDEFINED)
if (rightConstVal!=null && rightConstVal.number.toDouble() == 0.0)
jumpIfZeroOrNot(left, operator, jumpIfFalseLabel)
else
jumpIfComparison(left, operator, right, jumpIfFalseLabel, leftConstVal, rightConstVal)
}
private fun jumpIfZeroOrNot(
left: Expression,
operator: String,
jumpIfFalseLabel: String
) {
when(val dt = left.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.UWORD -> {
if(operator=="<") {
asmgen.out(" jmp $jumpIfFalseLabel")
return
} else if(operator==">=") {
return
}
if(dt==DataType.UBYTE) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A, false)
if (left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
} else {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, false)
asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1")
}
when (operator) {
"==" -> asmgen.out(" bne $jumpIfFalseLabel")
"!=" -> asmgen.out(" beq $jumpIfFalseLabel")
">" -> asmgen.out(" beq $jumpIfFalseLabel")
"<=" -> asmgen.out(" bne $jumpIfFalseLabel")
else -> throw AssemblyError("invalid comparison operator $operator")
}
}
DataType.BYTE -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A, true)
if (left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
when (operator) {
"==" -> asmgen.out(" bne $jumpIfFalseLabel")
"!=" -> asmgen.out(" beq $jumpIfFalseLabel")
">" -> asmgen.out(" beq $jumpIfFalseLabel | bmi $jumpIfFalseLabel")
"<" -> asmgen.out(" bpl $jumpIfFalseLabel")
">=" -> asmgen.out(" bmi $jumpIfFalseLabel")
"<=" -> asmgen.out("""
beq +
bpl $jumpIfFalseLabel
+ """)
else -> throw AssemblyError("invalid comparison operator $operator")
}
}
DataType.WORD -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, true)
when (operator) {
"==" -> asmgen.out(" bne $jumpIfFalseLabel | cpy #0 | bne $jumpIfFalseLabel")
"!=" -> asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1 | beq $jumpIfFalseLabel")
">" -> asmgen.out("""
cpy #0
bmi $jumpIfFalseLabel
bne +
cmp #0
beq $jumpIfFalseLabel
+ """)
"<" -> asmgen.out(" cpy #0 | bpl $jumpIfFalseLabel")
">=" -> asmgen.out(" cpy #0 | bmi $jumpIfFalseLabel")
"<=" -> asmgen.out("""
cpy #0
bmi +
bne $jumpIfFalseLabel
cmp #0
bne $jumpIfFalseLabel
+ """)
else -> throw AssemblyError("invalid comparison operator $operator")
}
}
DataType.FLOAT -> {
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1)
asmgen.out(" jsr floats.SIGN") // SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
when (operator) {
"==" -> asmgen.out(" bne $jumpIfFalseLabel")
"!=" -> asmgen.out(" beq $jumpIfFalseLabel")
">" -> asmgen.out(" bmi $jumpIfFalseLabel | beq $jumpIfFalseLabel")
"<" -> asmgen.out(" bpl $jumpIfFalseLabel")
">=" -> asmgen.out(" bmi $jumpIfFalseLabel")
"<=" -> asmgen.out(" cmp #1 | beq $jumpIfFalseLabel")
else -> throw AssemblyError("invalid comparison operator $operator")
}
}
else -> {
throw AssemblyError("invalid dt")
}
}
}
private fun jumpIfComparison(
left: Expression,
operator: String,
right: Expression,
jumpIfFalseLabel: String,
leftConstVal: NumericLiteralValue?,
rightConstVal: NumericLiteralValue?
) {
val dt = left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
when (operator) {
"==" -> {
// if the left operand is an expression, and the right is 0, we can just evaluate that expression,
// and use the result value directly to determine the boolean result. Shortcut only for integers.
if(rightConstVal?.number?.toDouble() == 0.0) {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
asmgen.out(" bne $jumpIfFalseLabel")
return
}
else if(dt in WordDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
bne $jumpIfFalseLabel""")
return
}
}
when (dt) {
in ByteDatatypes -> translateByteEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
in WordDatatypes -> translateWordEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
@ -89,26 +182,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
"!=" -> {
// if the left operand is an expression, and the right is 0, we can just evaluate that expression,
// and use the result value directly to determine the boolean result. Shortcut only for integers.
if(rightConstVal?.number?.toDouble() == 0.0) {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall && !left.isSimple)
asmgen.out(" cmp #0")
asmgen.out(" beq $jumpIfFalseLabel")
return
}
else if(dt in WordDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
asmgen.out("""
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
beq $jumpIfFalseLabel""")
return
}
}
when (dt) {
in ByteDatatypes -> translateByteNotEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
in WordDatatypes -> translateWordNotEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
@ -1625,7 +1698,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
private fun translateExpression(typecast: TypecastExpression) {
translateExpression(typecast.expression)
translateExpressionInternal(typecast.expression)
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> {
when(typecast.type) {
@ -1775,7 +1848,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
private fun translateExpression(expr: BinaryExpression) {
// TODO needs to use optimized assembly generation like the assignment instructions. But avoid code duplication.... rewrite all expressions into assignment form?
// Uses evalstack to evaluate the given expression.
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
@ -1784,14 +1858,13 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
// see if we can apply some optimized routines
// TODO avoid using evaluation on stack everywhere
when(expr.operator) {
"+" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVal = expr.left.constValue(program)?.number?.toInt()
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (leftVal!=null && leftVal in -4..4) {
translateExpression(expr.right)
translateExpressionInternal(expr.right)
if(rightDt in ByteDatatypes) {
val incdec = if(leftVal<0) "dec" else "inc"
repeat(leftVal.absoluteValue) {
@ -1821,7 +1894,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
else if (rightVal!=null && rightVal in -4..4)
{
translateExpression(expr.left)
translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "dec" else "inc"
repeat(rightVal.absoluteValue) {
@ -1856,7 +1929,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (rightVal!=null && rightVal in -4..4)
{
translateExpression(expr.left)
translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "inc" else "dec"
repeat(rightVal.absoluteValue) {
@ -1889,7 +1962,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
">>" -> {
val amount = expr.right.constValue(program)?.number?.toInt()
if(amount!=null) {
translateExpression(expr.left)
translateExpressionInternal(expr.left)
when (leftDt) {
DataType.UBYTE -> {
if (amount <= 2)
@ -1960,7 +2033,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
"<<" -> {
val amount = expr.right.constValue(program)?.number?.toInt()
if(amount!=null) {
translateExpression(expr.left)
translateExpressionInternal(expr.left)
if (leftDt in ByteDatatypes) {
if (amount <= 2)
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
@ -1997,7 +2070,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val amount = value.number.toInt()
if(amount==2) {
// optimize x*2 common case
translateExpression(expr.left)
translateExpressionInternal(expr.left)
if(leftDt in ByteDatatypes) {
asmgen.out(" asl P8ESTACK_LO+1,x")
} else {
@ -2008,38 +2081,38 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
when(rightDt) {
DataType.UBYTE -> {
if(amount in asmgen.optimizedByteMultiplications) {
translateExpression(expr.left)
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_byte_$amount")
return
}
}
DataType.BYTE -> {
if(amount in asmgen.optimizedByteMultiplications) {
translateExpression(expr.left)
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_byte_$amount")
return
}
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
translateExpression(expr.left)
translateExpressionInternal(expr.left)
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
return
}
}
DataType.UWORD -> {
if(amount in asmgen.optimizedWordMultiplications) {
translateExpression(expr.left)
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_word_$amount")
return
}
}
DataType.WORD -> {
if(amount in asmgen.optimizedWordMultiplications) {
translateExpression(expr.left)
translateExpressionInternal(expr.left)
asmgen.out(" jsr math.stack_mul_word_$amount")
return
}
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
translateExpression(expr.left)
translateExpressionInternal(expr.left)
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
return
}
@ -2053,7 +2126,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if(rightVal!=null && rightVal==2) {
translateExpression(expr.left)
translateExpressionInternal(expr.left)
when(leftDt) {
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
DataType.BYTE -> asmgen.out(" asl P8ESTACK_LO+1,x | ror P8ESTACK_LO+1,x")
@ -2075,9 +2148,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
translateCompareStrings(expr.left, expr.operator, expr.right)
}
else {
// the general, non-optimized cases TODO optimize more cases....
translateExpression(expr.left)
translateExpression(expr.right)
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
translateExpressionInternal(expr.left)
translateExpressionInternal(expr.right)
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
@ -2104,7 +2177,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
private fun translateExpression(expr: PrefixExpression) {
translateExpression(expr.expression)
translateExpressionInternal(expr.expression)
val itype = expr.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
@ -2189,8 +2262,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
fun translateExpression(indexer: ArrayIndex) = asmgen.translateExpression(indexer.indexExpr)
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")

View File

@ -8,8 +8,8 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.ForLoop
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.astprocessing.toConstantIntegerRange
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.toConstantIntegerRange
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -20,7 +20,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
throw AssemblyError("unknown dt")
when(stmt.iterable) {
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget)
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
} else {

View File

@ -9,12 +9,12 @@ import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError
import prog8.compiler.target.CpuType
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.CpuType
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -64,7 +64,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
require(sub.isAsmSubroutine)
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
@ -135,14 +135,25 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
// TODO find another way to prepare the arguments, without using the eval stack
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
// TODO here's an alternative to the above, but for now generates bigger code due to intermediate register steps:
// for (arg in stmt.args.reversed()) {
// // note this stuff below is needed to (eventually) avoid calling asmgen.translateExpression()
// // TODO also This STILL requires the translateNormalAssignment() to be fixed to avoid stack eval for expressions...
// val dt = arg.inferType(program).getOr(DataType.UNDEFINED)
// asmgen.assignExpressionTo(arg, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, dt, sub))
// }
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
@ -325,7 +336,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
else
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
AsmAssignTarget.fromRegisters(register, false, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)

View File

@ -6,7 +6,7 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.AssemblyError
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {

View File

@ -1,11 +1,11 @@
package prog8.compiler.target.cpu6502.codegen.assignment
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compilerinterface.IMemSizer
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
@ -41,11 +41,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
else
asmgen.asmVariableName(array.arrayvar)
}
lateinit var origAssign: AsmAssignment
@ -68,14 +69,14 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
}
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
when(registers) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
RegisterOrPair.FAC1,
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
RegisterOrPair.R0,
@ -93,7 +94,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
}
}
}

View File

@ -5,12 +5,12 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.CpuType
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,
@ -33,6 +33,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
fun translateNormalAssignment(assign: AsmAssignment) {
if(assign.isAugmentable) {
augmentableAsmGen.translate(assign)
return
}
when(assign.source.kind) {
SourceStorageKind.LITERALNUMBER -> {
// simple case: assign a constant number
@ -171,13 +176,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
// copy the actual string result into the target string variable
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
else -> throw AssemblyError("weird target dt")
}
@ -249,30 +254,40 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(assign.target, assign.target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(assign.target, assign.target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(assign.target, assign.target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
else -> {
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions to do it via registers here,
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
if (value.parent is Return) {
if (this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for return: $value target=${assign.target.kind} at ${value.position}")
}
exprAsmgen.translateExpression(value)
// TODO FIX THIS... by using a temp var? so that it becomes augmentable assignment expression?
asmgen.translateExpression(value)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
assignStackValue(assign.target)
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
}
}
}
SourceStorageKind.REGISTER -> {
when(assign.source.datatype) {
DataType.UBYTE -> assignRegisterByte(assign.target, assign.source.register!!.asCpuRegister())
DataType.UWORD -> assignRegisterpairWord(assign.target, assign.source.register!!)
else -> throw AssemblyError("invalid register dt")
}
asmgen.assignRegister(assign.source.register!!, assign.target)
}
SourceStorageKind.STACK -> {
assignStackValue(assign.target)
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
}
}
}
@ -364,16 +379,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when (valueDt) {
in ByteDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.A)
assignExpressionToRegister(value, RegisterOrPair.A, valueDt==DataType.BYTE)
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.A, valueDt)
}
in WordDatatypes -> {
assignExpressionToRegister(value, RegisterOrPair.AY)
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt==DataType.WORD)
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, valueDt)
}
DataType.FLOAT -> {
assignExpressionToRegister(value, RegisterOrPair.FAC1)
assignTypecastedFloatFAC1(target.asmVarname, targetDt)
assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
assignTypeCastedFloatFAC1(target.asmVarname, targetDt)
}
in PassByReferenceDatatypes -> {
// str/array value cast (most likely to UWORD, take address-of)
@ -399,14 +414,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.X,
RegisterOrPair.Y -> {
// 'cast' an ubyte value to a byte register; no cast needed at all
return assignExpressionToRegister(value, target.register)
return assignExpressionToRegister(value, target.register, false)
}
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// cast an ubyte value to a 16 bits register, just assign it and make use of the value extension
return assignExpressionToRegister(value, target.register!!)
return assignExpressionToRegister(value, target.register!!, false)
}
else -> {}
}
@ -424,19 +439,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// 'cast' uword into a 16 bits register, just assign it
return assignExpressionToRegister(value, target.register!!)
return assignExpressionToRegister(value, target.register!!, false)
}
else -> {}
}
}
// give up, do it via eval stack
// No more special optmized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P
// TODO optimize typecasts for more special cases?
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for typecast: $value into $targetDt (target=${target.kind} at ${value.position}")
asmgen.translateExpression(origTypeCastExpression) // this performs the actual type cast in translateExpression(Typecast)
assignStackValue(target)
asmgen.assignExpressionTo(origTypeCastExpression, target)
}
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
@ -447,7 +458,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign)
}
private fun assignTypecastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
private fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
if(targetDt==DataType.FLOAT)
throw AssemblyError("typecast to identical type")
@ -954,7 +965,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
lda #<$sourceName
ldy #>$sourceName+1
sta P8ESTACK_LO,x
sty P8ESTACK_HI,x
tya
sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("string-assign to weird target")
@ -1363,7 +1375,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// these will be correctly typecasted from a byte to a word value
if(target.register !in Cx16VirtualRegisters &&
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
if(target.kind==TargetStorageKind.VARIABLE) {
if(target.kind== TargetStorageKind.VARIABLE) {
val parts = target.asmVarname.split('.')
if (parts.size != 2 || parts[0] != "cx16")
require(target.datatype in ByteDatatypes)
@ -1459,7 +1471,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype in NumericDatatypes)
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes)
if(target.datatype==DataType.FLOAT)
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
@ -1586,7 +1598,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.STACK -> {
when(regs) {
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
@ -2157,9 +2169,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) {
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign)
}
@ -2171,8 +2183,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign)
}
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) {
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
translateNormalAssignment(assign)

View File

@ -5,10 +5,10 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.CpuType
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,
@ -181,8 +181,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
else -> {
asmgen.translateExpression(memory.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
// TODO OTHER EVALUATION HERE, don't use the estack
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1") // TODO don't use estack to transfer the address to read from
when {
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
@ -193,7 +194,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
}
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx")
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx") // TODO don't use estack to transfer the address to read from
}
}
}
@ -246,19 +247,37 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
indexVar!=null -> {
when (target.datatype) {
in ByteDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen)
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.A,
target.datatype == DataType.BYTE, null,
program,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
}
in WordDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen)
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.AY,
target.datatype == DataType.WORD, null,
program,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.FAC1, null, program, asmgen)
val tgt =
AsmAssignTarget.fromRegisters(
RegisterOrPair.FAC1,
true, null,
program,
asmgen
)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target)
@ -1705,7 +1724,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
internal fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
when (dt) {
DataType.UBYTE -> {
when (target.kind) {
@ -1749,9 +1768,30 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
}
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of ubyte array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> asmgen.out("""
cmp #0
beq +
lda #1
+ eor #1""")
RegisterOrPair.X -> asmgen.out("""
txa
beq +
lda #1
+ eor #1
tax""")
RegisterOrPair.Y -> asmgen.out("""
tya
beq +
lda #1
+ eor #1
tay""")
else -> throw AssemblyError("invalid reg dt for byte not")
}
}
TargetStorageKind.STACK -> TODO("missing codegen for byte stack not")
else -> throw AssemblyError("missing codegen for in-place not of ubyte ${target.kind}")
}
}
DataType.UWORD -> {
@ -1767,17 +1807,56 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
lsr a
sta ${target.asmVarname}+1""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory not")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of uword array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
ora P8ZP_SCRATCH_REG
beq +
lda #0
tax
beq ++
+ lda #1
+""")
}
RegisterOrPair.AY -> {
asmgen.out("""
sty P8ZP_SCRATCH_REG
ora P8ZP_SCRATCH_REG
beq +
lda #0
tay
beq ++
+ lda #1
+""")
}
RegisterOrPair.XY -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
tya
ora P8ZP_SCRATCH_REG
beq +
ldy #0
ldx #0
beq ++
+ ldx #1
+""")
}
in Cx16VirtualRegisters -> TODO()
else -> throw AssemblyError("invalid reg dt for word not")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory not")
TargetStorageKind.STACK -> TODO("missing codegen for word stack not")
else -> throw AssemblyError("missing codegen for in-place not of uword for ${target.kind}")
}
}
else -> throw AssemblyError("boolean-not of invalid type")
}
}
private fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
when (dt) {
DataType.UBYTE -> {
when (target.kind) {
@ -1812,9 +1891,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
}
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert ubyte array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> asmgen.out(" eor #255")
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay")
else -> throw AssemblyError("invalid reg dt for byte invert")
}
}
TargetStorageKind.STACK -> TODO("missing codegen for byte stack invert")
else -> throw AssemblyError("missing codegen for in-place invert ubyte for ${target.kind}")
}
}
DataType.UWORD -> {
@ -1828,17 +1914,27 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
eor #255
sta ${target.asmVarname}+1""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory invert")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert uword array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
in Cx16VirtualRegisters -> {
TODO("codegen for cx16 word register invert")
}
else -> throw AssemblyError("invalid reg dt for word invert")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory invert")
TargetStorageKind.STACK -> TODO("missing codegen for word stack invert")
else -> throw AssemblyError("missing codegen for in-place invert uword for ${target.kind}")
}
}
else -> throw AssemblyError("invert of invalid type")
}
}
private fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
when (dt) {
DataType.BYTE -> {
when (target.kind) {
@ -1849,10 +1945,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc ${target.asmVarname}
sta ${target.asmVarname}""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't in-place negate memory ubyte")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate byte array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> asmgen.out(" sta P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1")
RegisterOrPair.X -> asmgen.out(" stx P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tax")
RegisterOrPair.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tay")
else -> throw AssemblyError("invalid reg dt for byte negate")
}
}
TargetStorageKind.MEMORY -> TODO("can't in-place negate memory ubyte")
TargetStorageKind.STACK -> TODO("missing codegen for byte stack negate")
else -> throw AssemblyError("missing codegen for in-place negate byte array")
}
}
DataType.WORD -> {
@ -1867,10 +1970,55 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc ${target.asmVarname}+1
sta ${target.asmVarname}+1""")
}
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate word array")
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for word memory negate")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
TargetStorageKind.REGISTER -> {
when(target.register!!) { //P8ZP_SCRATCH_REG
RegisterOrPair.AX -> {
asmgen.out("""
sta P8ZP_SCRATCH_REG
stx P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
pha
lda #0
sbc P8ZP_SCRATCH_REG+1
tax
pla""")
}
RegisterOrPair.AY -> {
asmgen.out("""
sta P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
pha
lda #0
sbc P8ZP_SCRATCH_REG+1
tay
pla""")
}
RegisterOrPair.XY -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
tax
lda #0
sbc P8ZP_SCRATCH_REG+1
tay""")
}
in Cx16VirtualRegisters -> {
TODO("codegen for cx16 word register negate")
}
else -> throw AssemblyError("invalid reg dt for word neg")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for word memory negate")
TargetStorageKind.STACK -> TODO("missing codegen for word stack negate")
else -> throw AssemblyError("missing codegen for in-place negate word array")
}
}
DataType.FLOAT -> {
@ -1883,9 +2031,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}+1
""")
}
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate float array")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack float negate")
else -> throw AssemblyError("weird target kind for float")
TargetStorageKind.REGISTER -> TODO("missing codegen for float reg negate")
TargetStorageKind.MEMORY -> TODO("missing codegen for float memory negate")
TargetStorageKind.STACK -> TODO("missing codegen for stack float negate")
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
}
}
else -> throw AssemblyError("negate of invalid type")

View File

@ -1,14 +1,13 @@
package prog8.compiler.target.cx16
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
internal object CX16MachineDefinition: IMachineDefinition {
object CX16MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU65c02
@ -88,7 +87,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
"rmb", "smb", "stp", "wai")
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7a // temp storage for a single byte
override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
@ -98,37 +97,28 @@ internal object CX16MachineDefinition: IMachineDefinition {
init {
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
throw InternalCompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22..0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22..0x7f)
free.addAll(0xa9..0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22..0x7f)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
}
require(SCRATCH_B1 !in free)
require(SCRATCH_REG !in free)
require(SCRATCH_W1 !in free)
require(SCRATCH_W2 !in free)
for (reserved in options.zpReserved)
reserve(reserved)
removeReservedFromFreePool()
}
}
}

View File

@ -1,4 +1,4 @@
package prog8tests
package prog8tests.asmgen
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
@ -6,18 +6,23 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.base.RegisterOrPair
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.*
import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.asmgen.helpers.DummyFunctions
import prog8tests.asmgen.helpers.DummyMemsizer
import prog8tests.asmgen.helpers.DummyStringEncoder
import prog8tests.asmgen.helpers.ErrorReporterForTests
import java.nio.file.Path
@ -63,20 +68,20 @@ locallabel:
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
val subroutine = Subroutine("start", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
val labelInBlock = Label("label_outside", Position.DUMMY)
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
return program
}
private fun createTestAsmGen(program: Program): AsmGen {
val errors = ErrorReporter()
val errors = ErrorReporterForTests()
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
val zp = C64MachineDefinition.C64Zeropage(options)
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of(""))

View File

@ -0,0 +1,37 @@
package prog8tests.asmgen.helpers
import prog8.ast.IBuiltinFunctions
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.compilerinterface.IMemSizer
import prog8.ast.base.DataType
import prog8.compilerinterface.IStringEncoding
internal val DummyFunctions = object : IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(
name: String,
args: List<Expression>,
position: Position,
): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return ""
}
}

View File

@ -0,0 +1,30 @@
package prog8tests.asmgen.helpers
import prog8.ast.base.Position
import prog8.compilerinterface.IErrorReporter
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) {
errors.add("${position.toClickableStr()} $msg")
}
override fun warn(msg: String, position: Position) {
warnings.add("${position.toClickableStr()} $msg")
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
errors.clear()
warnings.clear()
}
}

View File

@ -0,0 +1,54 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
dependencies {
implementation project(':compilerInterfaces')
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 {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
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"
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<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>

View File

@ -1,22 +1,24 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.augmentAssignmentOperators
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.isInRegularRAMof
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
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...:
// 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) {
@ -38,6 +40,11 @@ internal class BinExprSplitter(private val program: Program, private val compTar
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,
@ -65,7 +72,7 @@ X = BinExpr X = LeftExpr
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as INameScope)
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as IStatementContainer)
)
}
}

View File

@ -12,7 +12,7 @@ import prog8.ast.walk.IAstModification
import kotlin.math.pow
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing

View File

@ -1,6 +1,5 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
@ -8,16 +7,20 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.astprocessing.size
import prog8.compiler.astprocessing.toConstantIntegerRange
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.size
import prog8.compilerinterface.toConstantIntegerRange
// Fix up the literal value's type to match that of the vardecl
// (also check range literal operands types before they get expanded into arrays for instance)
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent is AnonymousScope)
throw FatalAstException("vardecl may no longer occur in anonymousscope")
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
@ -31,28 +34,6 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
errors.err(x.message, x.position)
}
// move vardecl to the containing subroutine and add initialization assignment in its place if needed
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val subroutine = decl.definingSubroutine as? INameScope
if(subroutine!=null && subroutine!==parent) {
val declValue = decl.value
decl.value = null
decl.allowInitializeWithZero = false
return if (declValue == null) {
listOf(
IAstModification.Remove(decl, parent as INameScope),
IAstModification.InsertFirst(decl, subroutine)
)
} else {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, subroutine)
)
}
}
}
return noModifications
}
@ -160,9 +141,9 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
@ -214,9 +195,9 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
errors.err("range expression size (${rangeExpr.size(compTarget)}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),

View File

@ -15,14 +15,25 @@ import kotlin.math.log2
import kotlin.math.pow
/*
todo add more expression optimizations
todo add more peephole expression optimizations
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*(&X) => X
X % 1 => 0
X / 1 => X
X ^ -1 => ~x
X >= 1 => X > 0
X < 1 => X <= 0
X + С1 == C2 => X == C2 - C1
((X + C1) + C2) => (X + (C1 + C2))
((X + C1) + (Y + C2)) => ((X + Y) + (C1 + C2))
*/
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
class ExpressionSimplifier(private val program: Program) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()

View File

@ -2,11 +2,12 @@ package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this)
if(errors.noErrors()) {
@ -40,9 +41,10 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
}
internal fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions,
compTarget: ICompilationTarget): Int {
fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions,
compTarget: ICompilationTarget
): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
@ -52,14 +54,14 @@ internal fun Program.optimizeStatements(errors: IErrorReporter,
return optimizationCount
}
internal fun Program.simplifyExpressions() : Int {
fun Program.simplifyExpressions() : Int {
val opti = ExpressionSimplifier(this)
opti.visit(this)
return opti.applyModifications()
}
internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this, compTarget)
fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this, options, compTarget)
opti.visit(this)
return opti.applyModifications()
}

View File

@ -1,7 +1,7 @@
package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
@ -10,20 +10,21 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.astprocessing.size
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.size
import kotlin.math.floor
internal const val retvarName = "prog8_retval"
internal class StatementOptimizer(private val program: Program,
class StatementOptimizer(private val program: Program,
private val errors: IErrorReporter,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget) : AstWalker() {
private val compTarget: ICompilationTarget
) : AstWalker() {
private val subsThatNeedReturnVariable = mutableSetOf<Triple<INameScope, DataType, Position>>()
private val subsThatNeedReturnVariable = mutableSetOf<Triple<IStatementContainer, DataType, Position>>()
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
@ -79,7 +80,7 @@ internal class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope))
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
}
@ -117,7 +118,7 @@ internal class StatementOptimizer(private val program: Program,
functionCallStatement.void, pos
)
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
)
}
@ -130,7 +131,7 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope))
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
return noModifications
@ -152,11 +153,11 @@ internal class StatementOptimizer(private val program: Program,
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsNoCodeNorVars)
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope))
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifStatement, parent as IStatementContainer))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsCodeOrVars) {
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) {
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
@ -184,20 +185,20 @@ internal class StatementOptimizer(private val program: Program,
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars) {
if(forLoop.body.isEmpty()) {
errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
// remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
}
}
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
if (range.size(compTarget) == 1) {
if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
@ -268,7 +269,7 @@ internal class StatementOptimizer(private val program: Program,
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope))
listOf(IAstModification.Remove(whileLoop, parent as IStatementContainer))
}
}
return noModifications
@ -277,14 +278,14 @@ internal class StatementOptimizer(private val program: Program,
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val iter = repeatLoop.iterations
if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars) {
if(repeatLoop.body.isEmpty()) {
errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
}
val iterations = iter.constValue(program)?.number?.toInt()
if (iterations == 0) {
errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
}
if (iterations == 1) {
errors.warn("iterations is always 1", iter.position)
@ -296,10 +297,10 @@ internal class StatementOptimizer(private val program: Program,
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope
val scope = jump.parent as IStatementContainer
val label = jump.identifier?.targetStatement(program)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, jump.definingScope))
return listOf(IAstModification.Remove(jump, scope))
return noModifications
}
@ -332,7 +333,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope))
IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
} else if (op2 == "-") {
// A = A +/- B - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
@ -343,7 +344,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope))
IAstModification.InsertAfter(assignment, subConstant, parent as IStatementContainer))
}
}
}
@ -365,7 +366,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) {
// remove assignment to self
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
val targetIDt = assignment.target.inferType(program)
@ -385,7 +386,7 @@ internal class StatementOptimizer(private val program: Program,
when (bexpr.operator) {
"+" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
@ -399,7 +400,7 @@ internal class StatementOptimizer(private val program: Program,
}
"-" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
@ -411,18 +412,18 @@ internal class StatementOptimizer(private val program: Program,
}
}
}
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
"<<" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
">>" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
}
@ -445,7 +446,7 @@ internal class StatementOptimizer(private val program: Program,
val assign = Assignment(tgt, value, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
)
}
@ -469,7 +470,7 @@ internal class StatementOptimizer(private val program: Program,
return super.after(returnStmt, parent)
}
private fun hasBreak(scope: INameScope): Boolean {
private fun hasBreak(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{

View File

@ -9,47 +9,49 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.CallGraph
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.isInRegularRAMof
internal class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget): AstWalker() {
class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget
): AstWalker() {
private val callgraph = CallGraph(program)
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
return if (!module.isLibrary && (module.containsNoCodeNorVars || callgraph.unused(module)))
listOf(IAstModification.Remove(module, module.definingScope))
listOf(IAstModification.Remove(module, parent as IStatementContainer))
else
noModifications
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt, parent as INameScope)
reportUnreachable(breakStmt, parent as IStatementContainer)
return emptyList()
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
reportUnreachable(jump, parent as INameScope)
reportUnreachable(jump, parent as IStatementContainer)
return emptyList()
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
reportUnreachable(returnStmt, parent as INameScope)
reportUnreachable(returnStmt, parent as IStatementContainer)
return emptyList()
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.last() == "exit")
reportUnreachable(functionCallStatement, parent as INameScope)
reportUnreachable(functionCallStatement, parent as IStatementContainer)
return emptyList()
}
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
when(val next = parent.nextSibling(stmt)) {
private fun reportUnreachable(stmt: Statement, parent: IStatementContainer) {
when(val next = stmt.nextSibling()) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
else -> errors.warn("unreachable code", next.position)
}
@ -65,11 +67,11 @@ internal class UnusedCodeRemover(private val program: Program,
if (block.containsNoCodeNorVars) {
if(block.name != internedStringsModuleName)
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
if(callgraph.unused(block)) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
}
@ -84,16 +86,16 @@ internal class UnusedCodeRemover(private val program: Program,
if(subroutine.containsNoCodeNorVars) {
if(!subroutine.definingModule.isLibrary)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = mutableListOf(IAstModification.Remove(subroutine, subroutine.definingScope))
val removals = mutableListOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
callgraph.calledBy[subroutine]?.let {
for(node in it)
removals.add(IAstModification.Remove(node, node.definingScope))
removals.add(IAstModification.Remove(node, node.parent as IStatementContainer))
}
return removals
}
if(!subroutine.definingModule.isLibrary)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope))
return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
}
}
@ -107,7 +109,7 @@ internal class UnusedCodeRemover(private val program: Program,
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
if (callgraph.unused(decl)) {
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, decl.definingScope))
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
}
}
}

View File

@ -0,0 +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.

View File

@ -5,20 +5,19 @@ plugins {
id 'com.github.johnrengelman.shadow' version '7.1.0'
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenLocal()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':compilerInterfaces')
implementation project(':codeOptimizers')
implementation project(':compilerAst')
implementation project(':codeGeneration')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
@ -44,22 +43,6 @@ configurations {
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
sourceSets {
main {
java {
@ -84,11 +67,6 @@ application {
applicationName = 'p8compile'
}
artifacts {
archives shadowJar
}
shadowJar {
archiveBaseName = 'prog8compiler'
archiveVersion = prog8version
@ -108,3 +86,5 @@ test {
events "skipped", "failed"
}
}
build.finalizedBy installDist, installShadowDist

View File

@ -22,5 +22,8 @@
<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" />
</component>
</module>

View File

@ -12,6 +12,7 @@ floats {
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
ubyte[5] tempvar_swap_float ; used for some swap() operations
; ---- C64 basic and kernal ROM float constants and functions ----

View File

@ -10,10 +10,11 @@ floats {
; ---- this block contains C-64 compatible floating point related functions ----
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
ubyte[5] tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ----

View File

@ -6,10 +6,12 @@ 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!
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
%asm {{

View File

@ -1 +1 @@
7.1
7.2-dev

View File

@ -6,7 +6,6 @@ import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.parser.ParsingFailedError
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
@ -36,10 +35,12 @@ private fun compileMain(args: Array<String>): Boolean {
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try {
@ -74,7 +75,10 @@ private fun compileMain(args: Array<String>): Boolean {
val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
val compilationResult = compileProgram(filepath,
dontOptimize!=true, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
results.add(compilationResult)
}
@ -111,11 +115,12 @@ private fun compileMain(args: Array<String>): Boolean {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
compilationResult = compileProgram(filepath,
dontOptimize!=true, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
if(!compilationResult.success)
return false
} catch (x: ParsingFailedError) {
return false
} catch (x: AstException) {
return false
}

View File

@ -1,3 +0,0 @@
package prog8.compiler
internal class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -1,6 +1,7 @@
package prog8.compiler
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
@ -9,28 +10,18 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.astprocessing.isSubroutineParameter
import prog8.compilerinterface.*
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
private val errors: IErrorReporter) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
subroutineVariables.add(decl.name to decl)
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// A numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value.
// This allows you to restart the program and have the same starting values of the variables
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope.nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
decl.value = null
else {
decl.value = decl.zeroElementValue()
}
}
}
return noModifications
}
@ -40,8 +31,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& assignment.target.isInRegularRAMof(compTarget.machine)) {
&& assignment.target.isInRegularRAMof(options.compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) {
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
@ -52,14 +47,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// use the other part of the expression to split.
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope),
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)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope),
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
@ -74,33 +69,35 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
addedIfConditionVars.clear()
if(!subroutine.isAsmSubroutine) {
// change 'str' parameters into 'uword' (just treat it as an address)
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
val parameterChanges = stringParams.map {
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
IAstModification.ReplaceNode(it, uwordParam, subroutine)
}
val stringParamNames = stringParams.map { it.name }.toSet()
val varsChanges = subroutine.statements
.filterIsInstance<VarDecl>()
.filter { it.autogeneratedDontRemove && it.name in stringParamNames }
.map {
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, it.position)
IAstModification.ReplaceNode(it, newvar, subroutine)
}
return parameterChanges + varsChanges
}
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
subroutineVariables.addAll(decls.map { it.name to it })
val sub = scope.definingSubroutine
if (sub != null) {
// move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same!
val replacements = mutableListOf<IAstModification>()
val movements = mutableListOf<IAstModification.InsertFirst>()
for(decl in decls) {
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
} else {
replacements.add(IAstModification.Remove(decl, scope))
}
movements.add(IAstModification.InsertFirst(decl, sub))
}
return replacements + movements
}
return noModifications
}
@ -146,7 +143,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove superfluous 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.
// 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
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
@ -155,22 +153,23 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
}
}
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
// that the types of assignment values and their target are the same,
// and that the types of both operands of a binaryexpression node are the same.
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
// The only place for now where we can do this is for:
// asmsub register pair parameter.
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
if(typecast.expression is IdentifierReference) {
return listOf(IAstModification.ReplaceNode(
val identifier = typecast.expression as? IdentifierReference
if(identifier!=null) {
return if(identifier.isSubroutineParameter(program)) {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
typecast.expression,
parent
))
))
} else {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, typecast.position),
parent
))
}
} else if(typecast.expression is IFunctionCall) {
return listOf(IAstModification.ReplaceNode(
typecast,
@ -186,7 +185,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
return noModifications
}
@Suppress("DuplicatedCode")
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
val prefixExpr = ifStatement.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// if not x -> if x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
val binExpr = ifStatement.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// if x -> if x!=0, if x+5 -> if x+5 != 0
@ -197,52 +204,20 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
(binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0)
throw CompilerException("if 0==X should have been swapped to if X==0")
throw InternalCompilerException("if 0==X should have been swapped to if X==0")
// split the conditional expression into separate variables if the operand(s) is not simple.
// DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE IF X {...} or IF NOT X {...}
// val modifications = mutableListOf<IAstModification>()
// if(!binExpr.left.isSimple) {
// val sub = binExpr.definingSubroutine()!!
// val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left)
// if(isNew)
// modifications.add(IAstModification.InsertFirst(variable, sub))
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
// modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
// addedIfConditionVars.add(Pair(sub, variable.name))
// }
// if(!binExpr.right.isSimple) {
// val sub = binExpr.definingSubroutine()!!
// val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right)
// if(isNew)
// modifications.add(IAstModification.InsertFirst(variable, sub))
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
// modifications.add(IAstModification.ReplaceNode(binExpr.right, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
// addedIfConditionVars.add(Pair(sub, variable.name))
// }
// return modifications
return noModifications
}
// private fun addIfOperandVar(sub: Subroutine, side: String, operand: Expression): Triple<VarDecl, Boolean, Assignment> {
// val dt = operand.inferType(program).typeOrElse(DataType.UNDEFINED)
// val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}"
// val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position)
// val assign = Assignment(tgt, operand, operand.position)
// if(Pair(sub, varname) in addedIfConditionVars) {
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
// return Triple(vardecl, false, assign)
// }
// val existing = sub.statements.firstOrNull { it is VarDecl && it.name == varname} as VarDecl?
// return if (existing == null) {
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
// Triple(vardecl, true, assign)
// } else {
// Triple(existing, false, assign)
// }
// }
@Suppress("DuplicatedCode")
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = untilLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// until not x -> until x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
val binExpr = untilLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// until x -> until x!=0, until x+5 -> until x+5 != 0
@ -252,7 +227,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
return noModifications
}
@Suppress("DuplicatedCode")
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = whileLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// while not x -> while x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
val binExpr = whileLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// while x -> while x!=0, while x+5 -> while x+5 != 0
@ -347,7 +330,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
// 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 assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope))
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
return modifications
}

View File

@ -1,9 +1,8 @@
package prog8.compiler
import com.github.michaelbull.result.*
import prog8.ast.AstToSourceCode
import prog8.ast.AstToSourceTextConverter
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.Position
@ -11,73 +10,39 @@ import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive
import prog8.compiler.astprocessing.*
import prog8.compiler.functions.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.asmGeneratorFor
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.*
import prog8.optimizer.*
import prog8.parser.ParseError
import prog8.parser.ParsingFailedError
import prog8.parser.SourceCode
import prog8.parser.SourceCode.Companion.libraryFilePrefix
import java.io.File
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension
import kotlin.system.measureTimeMillis
enum class OutputType {
RAW,
PRG
}
enum class LauncherType {
BASIC,
NONE
}
enum class ZeropageType {
BASICSAFE,
FLOATSAFE,
KERNALSAFE,
FULL,
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
}
class CompilerException(message: String?) : Exception(message)
class CompilationResult(val success: Boolean,
val programAst: Program,
val program: Program,
val programName: String,
val compTarget: ICompilationTarget,
val importedFiles: List<Path>)
// TODO refactor the gigantic list of parameters
fun compileProgram(filepath: Path,
optimize: Boolean,
optimizeFloatExpressions: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
quietAssembler: Boolean,
compilationTarget: String,
sourceDirs: List<String>,
outputDir: Path): CompilationResult {
outputDir: Path,
errors: IErrorReporter = ErrorReporter()): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var program: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
val compTarget =
when(compilationTarget) {
@ -89,31 +54,35 @@ fun compileProgram(filepath: Path,
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
val (programresult, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
with(compilationOptions) {
this.slowCodegenWarnings = slowCodegenWarnings
this.optimize = optimize
this.optimizeFloatExpressions = optimizeFloatExpressions
}
program = programresult
importedFiles = imported
processAst(programAst, errors, compilationOptions)
processAst(program, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(
programAst,
program,
compilationOptions,
errors,
BuiltinFunctionsFacade(BuiltinFunctions),
compTarget,
compilationOptions
compTarget
)
postprocessAst(programAst, errors, compilationOptions)
postprocessAst(program, errors, compilationOptions)
// printAst(programAst)
// println("*********** AST BEFORE ASSEMBLYGEN *************")
// printAst(program)
if (writeAssembly) {
val result = writeAssembly(programAst, errors, outputDir, compilationOptions)
val result = writeAssembly(program, errors, outputDir, quietAssembler, compilationOptions)
when (result) {
is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> {
System.err.println(result.error)
return CompilationResult(false, programAst, programName, compTarget, importedFiles)
return CompilationResult(false, program, programName, compTarget, importedFiles)
}
}
}
@ -121,14 +90,20 @@ fun compileProgram(filepath: Path,
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
return CompilationResult(true, program, programName, compTarget, importedFiles)
} catch (px: ParseError) {
System.err.print("\u001b[91m") // bright red
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
System.err.print("\u001b[0m") // reset
} catch (pfx: ParsingFailedError) {
} catch (ac: AbortCompilation) {
if(!ac.message.isNullOrEmpty()) {
System.err.print("\u001b[91m") // bright red
System.err.println(ac.message)
System.err.print("\u001b[0m") // reset
}
} catch (nsf: NoSuchFileException) {
System.err.print("\u001b[91m") // bright red
System.err.println(pfx.message)
System.err.println("File not found: ${nsf.message}")
System.err.print("\u001b[0m") // reset
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
@ -148,7 +123,7 @@ fun compileProgram(filepath: Path,
throw x
}
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compTarget)
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
}
@ -158,13 +133,13 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
override val names = functions.keys
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? {
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue? {
val func = BuiltinFunctions[name]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null) {
return try {
exprfunc(args, position, program, memsizer)
exprfunc(args, position, program)
} catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
null
@ -188,21 +163,18 @@ fun parseImports(filepath: Path,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${compTarget.name}. Parsing...")
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
bf.program = programAst
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
bf.program = program
val importer = ModuleImporter(programAst, compTarget.name, errors, sourceDirs)
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
val importedModuleResult = importer.importModule(filepath)
importedModuleResult.onFailure { throw it }
errors.report()
val importedFiles = programAst.modules.map { it.source }
val importedFiles = program.modules.map { it.source }
.filter { it.isFromFilesystem }
.map { Path(it.origin) }
val compilerOptions = determineCompilationOptions(programAst, compTarget)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
val compilerOptions = determineCompilationOptions(program, compTarget)
// depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
importer.importLibraryModule(lib)
@ -210,8 +182,12 @@ fun parseImports(filepath: Path,
// always import prog8_lib and math
importer.importLibraryModule("math")
importer.importLibraryModule("prog8_lib")
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
errors.report()
return Triple(programAst, compilerOptions, importedFiles)
return Triple(program, compilerOptions, importedFiles)
}
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
@ -274,44 +250,44 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
)
}
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions)
program.preprocessAst()
program.checkIdentifiers(errors, compilerOptions)
errors.report()
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
// NOTE: we will then lose the opportunity to do constant-folding on any expression containing a char literal, but how often will those occur?
// Also they might be optimized away eventually in codegen or by the assembler even
programAst.charLiteralsToUByteLiterals(errors, compilerOptions.compTarget)
// ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
// By introduction a CHAR dt, we will also lose the opportunity to do constant-folding on any expression containing a char literal.
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
program.constantFold(errors, compilerOptions.compTarget)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)
program.reorderStatements(errors)
errors.report()
programAst.reorderStatements(errors)
program.addTypecasts(errors)
errors.report()
programAst.addTypecasts(errors)
program.variousCleanups(program, errors)
errors.report()
programAst.variousCleanups(programAst, errors)
program.checkValid(errors, compilerOptions)
errors.report()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions)
program.checkIdentifiers(errors, compilerOptions)
errors.report()
}
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) {
private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
// optimize the parse tree
println("Optimizing...")
val remover = UnusedCodeRemover(programAst, errors, compTarget)
remover.visit(programAst)
val remover = UnusedCodeRemover(program, errors, compTarget)
remover.visit(program)
remover.applyModifications()
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget)
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
val optsDone1 = program.simplifyExpressions()
val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget)
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
@ -320,17 +296,17 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
errors.report()
}
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.addTypecasts(errors)
errors.report()
programAst.variousCleanups(programAst, errors)
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
program.variousCleanups(program, errors)
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(programAst)
val callGraph = CallGraph(program)
callGraph.checkRecursiveCalls(errors)
errors.report()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
program.verifyFunctionArgTypes()
program.moveMainAndStartToFirst()
}
private sealed class WriteAssemblyResult {
@ -338,58 +314,56 @@ private sealed class WriteAssemblyResult {
class Fail(val error: String): WriteAssemblyResult()
}
private fun writeAssembly(programAst: Program,
private fun writeAssembly(program: Program,
errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions): WriteAssemblyResult {
quietAssembler: Boolean,
compilerOptions: CompilationOptions
): WriteAssemblyResult {
// asm generation directly from the Ast
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
program.processAstBeforeAsmGeneration(compilerOptions, errors)
errors.report()
// printAst(programAst)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printAst(program)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,
programAst,
program,
errors,
compilerOptions.compTarget.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
errors.report()
return if(assembly.valid && errors.noErrors()) {
val assemblerReturnStatus = assembly.assemble(compilerOptions)
val assemblerReturnStatus = assembly.assemble(quietAssembler, compilerOptions)
if(assemblerReturnStatus!=0)
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
else {
errors.report()
WriteAssemblyResult.Ok(assembly.name)
}
} else {
errors.report()
WriteAssemblyResult.Fail("compiler failed with errors")
}
}
fun printAst(programAst: Program) {
fun printAst(program: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
val printer = AstToSourceTextConverter(::print, program)
printer.visit(program)
println()
}
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(libraryFilePrefix)) {
return runCatching {
val stream = object {}.javaClass.getResourceAsStream("/prog8lib/${filename.substring(libraryFilePrefix.length)}") // TODO handle via SourceCode
stream!!.bufferedReader().use { r -> r.readText() }
}.mapError { NoSuchFileException(File(filename)) }
} else {
// first try in the isSameAs folder as where the containing file was imported from
val sib = Path(source.origin).resolveSibling(filename)
if (sib.toFile().isFile)
Ok(sib.toFile().readText())
else
Ok(File(filename).readText())
}
internal fun asmGeneratorFor(
compTarget: ICompilationTarget,
program: Program,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{
// at the moment we only have one code generation backend (for 6502 and 65c02)
return AsmGen(program, errors, zp, options, compTarget, outputDir)
}

View File

@ -1,16 +1,7 @@
package prog8.compiler
import prog8.ast.base.Position
import prog8.parser.ParsingFailedError
interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun noErrors(): Boolean
fun report()
}
import prog8.compilerinterface.IErrorReporter
internal class ErrorReporter: IErrorReporter {
private enum class MessageSeverity {
@ -33,24 +24,29 @@ internal class ErrorReporter: IErrorReporter {
var numErrors = 0
var numWarnings = 0
messages.forEach {
val printer = when(it.severity) {
MessageSeverity.WARNING -> System.out
MessageSeverity.ERROR -> System.err
}
when(it.severity) {
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
MessageSeverity.ERROR -> printer.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> printer.print("\u001b[93m") // bright yellow
}
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
if(msg !in alreadyReportedMessages) {
System.err.println(msg)
printer.println(msg)
alreadyReportedMessages.add(msg)
when(it.severity) {
MessageSeverity.WARNING -> numWarnings++
MessageSeverity.ERROR -> numErrors++
}
}
System.err.print("\u001b[0m") // reset color
printer.print("\u001b[0m") // reset color
}
System.out.flush()
System.err.flush()
messages.clear()
if(numErrors>0)
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
finalizeNumErrors(numErrors, numWarnings)
}
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }

View File

@ -7,6 +7,7 @@ import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg
import prog8.compilerinterface.IErrorReporter
import prog8.parser.Prog8Parser
import prog8.parser.SourceCode
import java.io.File
@ -33,7 +34,7 @@ class ModuleImporter(private val program: Program,
val srcPath = when (candidates.size) {
0 -> return Err(NoSuchFileException(
file = filePath.normalize().toFile(),
reason = "searched in $searchIn"))
reason = "Searched in $searchIn"))
1 -> candidates.first()
else -> candidates.first() // when more candiates, pick the one from the first location
}
@ -105,7 +106,7 @@ class ModuleImporter(private val program: Program,
importModule(it)
},
failure = {
errors.err("no module found with name $moduleName", import.position)
errors.err("no module found with name $moduleName. Searched in: $sourcePaths (and internal libraries)", import.position)
return null
}
)
@ -142,7 +143,6 @@ class ModuleImporter(private val program: Program,
} else {
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
sourcePaths.drop(dropCurDir) +
// TODO: won't work until Prog8Parser is fixed s.t. it fully initializes the modules it returns. // hm, what won't work?)
listOf(Path(importingModule.position.file).parent ?: Path("")) +
listOf(Path(".", "prog8lib"))
}

View File

@ -1,27 +1,22 @@
package prog8.compiler.astprocessing
import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.ZeropageType
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.*
import java.io.CharConversionException
import java.io.File
import java.util.*
import kotlin.io.path.*
import kotlin.io.path.Path
internal class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget
private val compilerOptions: CompilationOptions
) : IAstVisitor {
override fun visit(program: Program) {
@ -31,7 +26,7 @@ internal class AstChecker(private val program: Program,
if(mainBlocks.size>1)
errors.err("more than one 'main' block", mainBlocks[0].position)
if(mainBlocks.isEmpty())
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: Position.DUMMY)
for(mainBlock in mainBlocks) {
val startSub = mainBlock.subScope("start") as? Subroutine
@ -196,8 +191,12 @@ internal class AstChecker(private val program: Program,
is Label,
is VarDecl,
is InlineAssembly,
is INameScope,
is IStatementContainer,
is NopStatement -> true
is Assignment -> {
val target = statement.target.identifier!!.targetStatement(program)
target === statement.previousSibling() // an initializer assignment is okay
}
else -> false
}
if (!ok) {
@ -217,7 +216,7 @@ internal class AstChecker(private val program: Program,
super.visit(label)
}
private fun hasReturnOrJump(scope: INameScope): Boolean {
private fun hasReturnOrJump(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{
var count=0
@ -245,8 +244,8 @@ internal class AstChecker(private val program: Program,
if(subroutine.name in BuiltinFunctions)
err("cannot redefine a built-in function")
if(subroutine.parameters.size>16)
err("subroutines are limited to 16 parameters")
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine)
errors.warn("subroutine has a large number of parameters, this slows down code execution a lot", subroutine.position)
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
if(uniqueNames.size!=subroutine.parameters.size)
@ -290,7 +289,7 @@ internal class AstChecker(private val program: Program,
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
err("parameter '${param.first.name}' should be (u)word/address")
err("parameter '${param.first.name}' should be (u)word (an address) or str")
}
else if(param.second.statusflag!=null) {
if (param.first.type != DataType.UBYTE)
@ -382,10 +381,12 @@ internal class AstChecker(private val program: Program,
err("can only use Carry as status flag parameter")
} else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Non-string Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD).
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
for(p in subroutine.parameters) {
if(p.type in PassByReferenceDatatypes && p.type != DataType.STR) {
err("Non-string pass-by-reference types cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
}
}
}
}
@ -453,7 +454,7 @@ internal class AstChecker(private val program: Program,
val targetIdentifier = assignTarget.identifier
if (targetIdentifier != null) {
val targetName = targetIdentifier.nameInSource
when (val targetSymbol = program.namespace.lookup(targetName, assignment)) {
when (val targetSymbol = assignment.definingScope.lookup(targetName)) {
null -> {
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
return
@ -505,7 +506,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(decl: VarDecl) {
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
fun err(msg: String) = errors.err(msg, decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
@ -586,10 +587,10 @@ internal class AstChecker(private val program: Program,
val numvalue = decl.value as? NumericLiteralValue
if(numvalue!=null) {
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
err("memory address must be valid integer 0..\$ffff")
}
} else {
err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?", decl.value?.position)
err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?")
}
}
}
@ -597,7 +598,7 @@ internal class AstChecker(private val program: Program,
val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR) {
if (declValue.inferType(program) isnot decl.datatype) {
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})")
}
}
@ -626,10 +627,13 @@ internal class AstChecker(private val program: Program,
}
}
// string assignment is not supported in a vard
if(decl.datatype==DataType.STR) {
if(decl.value==null)
err("string var must be initialized with a string literal")
if(decl.value==null) {
// complain about uninitialized str, but only if it's a regular variable
val parameter = (decl.parent as? Subroutine)?.parameters?.singleOrNull{ it.name==decl.name }
if(parameter==null)
err("string var must be initialized with a string literal")
}
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
err("string var can only be initialized with a string literal")
}
@ -768,7 +772,7 @@ internal class AstChecker(private val program: Program,
override fun visit(char: CharLiteral) {
try { // just *try* if it can be encoded, don't actually do it
compTarget.encodeString(char.value.toString(), char.altEncoding)
compilerOptions.compTarget.encodeString(char.value.toString(), char.altEncoding)
} catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode character", char.position)
}
@ -780,7 +784,7 @@ internal class AstChecker(private val program: Program,
checkValueTypeAndRangeString(DataType.STR, string)
try { // just *try* if it can be encoded, don't actually do it
compTarget.encodeString(string.value, string.altEncoding)
compilerOptions.compTarget.encodeString(string.value, string.altEncoding)
} catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode string", string.position)
}
@ -789,11 +793,10 @@ internal class AstChecker(private val program: Program,
}
override fun visit(expr: PrefixExpression) {
val idt = expr.inferType(program)
if(!idt.isKnown)
val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
if(dt==DataType.UNDEFINED)
return // any error should be reported elsewhere
val dt = idt.getOr(DataType.UNDEFINED)
if(expr.operator=="-") {
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
errors.err("can only take negative of a signed number type", expr.position)
@ -1067,7 +1070,7 @@ internal class AstChecker(private val program: Program,
override fun visit(postIncrDecr: PostIncrDecr) {
if(postIncrDecr.target.identifier != null) {
val targetName = postIncrDecr.target.identifier!!.nameInSource
val target = program.namespace.lookup(targetName, postIncrDecr)
val target = postIncrDecr.definingScope.lookup(targetName)
if(target==null) {
val symbol = postIncrDecr.target.identifier!!
errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
@ -1254,7 +1257,7 @@ internal class AstChecker(private val program: Program,
// check if the floating point values are all within range
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
if(doubles.any { it < compTarget.machine.FLOAT_MAX_NEGATIVE || it > compTarget.machine.FLOAT_MAX_POSITIVE })
if(doubles.any { it < compilerOptions.compTarget.machine.FLOAT_MAX_NEGATIVE || it > compilerOptions.compTarget.machine.FLOAT_MAX_POSITIVE })
return err("floating point value overflow")
return true
}

View File

@ -3,74 +3,25 @@ package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.IStringEncoding
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.IMachineDefinition
import kotlin.math.abs
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.IStringEncoding
fun RangeExpr.size(encoding: IStringEncoding): Int? {
val fromLv = (from as? NumericLiteralValue)
val toLv = (to as? NumericLiteralValue)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange(encoding)?.count()
}
fun RangeExpr.toConstantIntegerRange(encoding: IStringEncoding): IntProgression? {
val fromVal: Int
val toVal: Int
val fromString = from as? StringLiteralValue
val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) {
// string range -> int range over character values
fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
if(fromLv==null || toLv==null)
return null // non-constant range
// integer range
fromVal = fromLv.number.toInt()
toVal = toLv.number.toInt()
}
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
return makeRange(fromVal, toVal, stepVal)
}
private fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
val checker = AstChecker(this, compilerOptions, errors, compTarget)
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
val checker = AstChecker(this, errors, compilerOptions)
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, compilerOptions, errors)
fixer.visit(this)
while(errors.noErrors() && fixer.applyModifications()>0) {
fixer.visit(this)
@ -88,7 +39,7 @@ internal fun Program.reorderStatements(errors: IErrorReporter) {
}
}
internal fun Program.charLiteralsToUByteLiterals(errors: IErrorReporter, enc: IStringEncoding) {
internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
val walker = object : AstWalker() {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.ReplaceNode(
@ -113,9 +64,17 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
internal fun Program.preprocessAst() {
val transforms = AstPreprocessor()
transforms.visit(this)
var mods = transforms.applyModifications()
while(mods>0)
mods = transforms.applyModifications()
}
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
val checker2 = AstIdentifiersChecker(errors, options.compTarget)
checker2.visit(this)
if(errors.noErrors()) {
@ -135,7 +94,6 @@ internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
process.applyModifications()
}
internal fun Program.moveMainAndStartToFirst() {
// the module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there,
@ -166,46 +124,10 @@ internal fun Program.moveMainAndStartToFirst() {
}
}
internal fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean {
val memAddr = memoryAddress
val arrayIdx = arrayindexed
val ident = identifier
when {
memAddr != null -> {
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val program = definingModule.program
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
arrayIdx != null -> {
val program = definingModule.program
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
ident != null -> {
val program = definingModule.program
val decl = ident.targetVarDecl(program)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
val vardecl = this.targetVarDecl(program)
if(vardecl!=null && vardecl.autogeneratedDontRemove) {
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
}
return false
}

View File

@ -1,15 +1,14 @@
package prog8.compiler.astprocessing
import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
internal class AstIdentifiersChecker(private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>()
private fun nameError(name: String, position: Position, existing: Statement) {
@ -42,7 +41,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
val existing = program.namespace.lookup(listOf(decl.name), decl)
val existing = decl.definingScope.lookup(listOf(decl.name))
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
@ -65,22 +64,19 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
val existing = program.namespace.lookup(listOf(subroutine.name), subroutine)
val existing = subroutine.lookup(listOf(subroutine.name))
if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing)
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters. Blocks are okay.
// check that there are no local symbols (variables, labels, subs) that redefine the subroutine's parameters.
val symbolsInSub = subroutine.allDefinedSymbols
val namesInSub = symbolsInSub.map{ it.first }.toSet()
val paramNames = subroutine.parameters.map { it.name }.toSet()
val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) {
val labelOrVar = subroutine.getLabelOrVariable(name)
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
nameError(name, labelOrVar.position, subroutine)
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
if(sub!=null)
nameError(name, subroutine.position, sub)
val symbol = subroutine.searchSymbol(name)
if(symbol!=null && symbol.position != subroutine.position)
nameError(name, symbol.position, subroutine)
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {

View File

@ -0,0 +1,48 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
class AstPreprocessor : AstWalker() {
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
// move vardecls in Anonymous scope up to the containing subroutine
// and add initialization assignment in its place if needed
val vars = scope.statements.filterIsInstance<VarDecl>()
val parentscope = scope.definingScope
if(vars.any() && parentscope !== parent) {
val movements = mutableListOf<IAstModification>()
val replacements = mutableListOf<IAstModification>()
for(decl in vars) {
if(decl.type != VarDeclType.VAR) {
movements.add(IAstModification.InsertFirst(decl, parentscope))
replacements.add(IAstModification.Remove(decl, scope))
} else {
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
} else {
replacements.add(IAstModification.Remove(decl, scope))
}
movements.add(IAstModification.InsertFirst(decl, parentscope))
}
}
return movements + replacements
}
return noModifications
}
}

View File

@ -67,7 +67,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
}
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
@ -99,12 +99,12 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
internal fun replacePointerVarIndexWithMemread(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
internal fun replacePointerVarIndexWithMemreadOrMemwrite(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
// rewrite pointervar[index] into @(pointervar+index)
val indexer = arrayIndexedExpression.indexer
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position)
val add = BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", indexer.indexExpr, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// we're part of the target of an assignment, we have to actually change the assign target itself
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)

View File

@ -1,5 +1,6 @@
package prog8.compiler.astprocessing
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
@ -40,11 +41,13 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
if(litval2!=null) {
if(array.parent !is IStatementContainer)
return noModifications
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope)
IAstModification.InsertFirst(vardecl2, array.parent as IStatementContainer)
)
}
}

View File

@ -1,17 +1,13 @@
package prog8.compiler.astprocessing
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.FatalAstException
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.IErrorReporter
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
@ -38,15 +34,62 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
module.statements.add(0, mainBlock)
}
reorderVardeclsAndDirectives(module.statements)
directivesToTheTop(module.statements)
return noModifications
}
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
private val declsProcessedWithInitAssignment = mutableSetOf<VarDecl>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
if(decl !in declsProcessedWithInitAssignment) {
declsProcessedWithInitAssignment.add(decl)
if (decl.value == null) {
if (!decl.autogeneratedDontRemove && decl.allowInitializeWithZero) {
// A numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar).
// This allows you to restart the program and have the same starting values of the variables
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
decl.value = null
if(decl.name.startsWith("retval_interm_") && decl.definingScope.name=="prog8_lib") {
// no need to zero out the special internal returnvalue intermediates.
// TODO these variables are no longer used???
return noModifications
}
val nextStmt = decl.nextSibling()
val nextAssign = nextStmt as? Assignment
val nextFor = nextStmt as? ForLoop
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
val hasNextAssignment = nextAssign!=null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY)
if (!hasNextAssignment && !hasNextForWithThisLoopvar) {
// Add assignment to initialize with zero
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
val identifier = IdentifierReference(listOf(decl.name), decl.position)
val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), decl.position)
return listOf(IAstModification.InsertAfter(
decl, assignzero, parent as IStatementContainer
))
}
}
} else {
// Transform the vardecl with initvalue to a plain vardecl + assignment
// this allows for other optimizations to kick in.
// So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99'
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, pos)
decl.value = null
return listOf(IAstModification.InsertAfter(
decl, assign, parent as IStatementContainer
))
}
}
}
return noModifications
}
private fun directivesToTheTop(statements: MutableList<Statement>) {
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives)
statements.addAll(0, directives)
@ -61,7 +104,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
)
}
reorderVardeclsAndDirectives(block.statements)
directivesToTheTop(block.statements)
return noModifications
}
@ -86,7 +129,12 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
if(parent !is VarDecl) {
// don't replace the initializer value in a vardecl - this will be moved to a separate
// assignment statement soon in after(VarDecl)
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {

View File

@ -8,8 +8,8 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.IErrorReporter
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
@ -78,10 +78,10 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment))
} else {
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> {
val cast = cvalue.cast(targettype)
fun castLiteral(cvalue2: NumericLiteralValue): List<IAstModification.ReplaceNode> {
val cast = cvalue2.cast(targettype)
return if(cast.isValid)
listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent))
listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment))
else
emptyList()
}
@ -135,11 +135,13 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
if(pair.second is IdentifierReference) {
// We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = pair.second as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(pair.second as IdentifierReference, pair.second.position),
AddressOf(identifier, pair.second.position),
call as Node)
}
} else if(pair.second is NumericLiteralValue) {

View File

@ -1,7 +1,7 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
@ -10,23 +10,23 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compilerinterface.IErrorReporter
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
return listOf(IAstModification.Remove(nopStatement, parent as IStatementContainer))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
return if(parent is IStatementContainer)
listOf(ScopeFlatten(scope, parent as IStatementContainer))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {

View File

@ -8,21 +8,21 @@ import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilerException
import prog8.compiler.functions.BuiltinFunctions
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.InternalCompilerException
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, program)
if(error!=null)
throw CompilerException(error)
throw InternalCompilerException(error)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, program)
if (error!=null)
throw CompilerException(error)
throw InternalCompilerException(error)
}
companion object {

View File

@ -1,17 +0,0 @@
package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"
internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
internal interface IAssemblyProgram {
val valid: Boolean
val name: String
fun assemble(options: CompilationOptions): Int
}

View File

@ -1,98 +0,0 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.AssignTarget
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.IStringEncoding
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.io.CharConversionException
import java.nio.file.Path
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
}
internal object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal fun asmGeneratorFor(
compTarget: ICompilationTarget,
program: Program,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{
// at the moment we only have one code generation backend (for 6502 and 65c02)
return AsmGen(program, errors, zp, options, compTarget, outputDir)
}

View File

@ -1,24 +1,28 @@
package prog8tests
import kotlin.test.*
import com.github.michaelbull.result.getErrorOrElse
import com.github.michaelbull.result.getOrElse
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.*
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.hamcrest.core.Is
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.*
import prog8.ast.Program
import prog8.ast.internedStringsModuleName
import prog8.compiler.IErrorReporter
import prog8.compiler.ModuleImporter
import prog8.compilerinterface.IErrorReporter
import prog8.parser.ParseError
import prog8.parser.SourceCode
import prog8tests.helpers.*
import prog8tests.ast.helpers.*
import prog8tests.helpers.ErrorReporterForTests
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
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -28,7 +32,7 @@ class TestModuleImporter {
private lateinit var program: Program
@BeforeEach
fun beforeEach() {
program = Program("foo", DummyFunctions, DummyMemsizer)
program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
}
private fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
@ -36,7 +40,7 @@ class TestModuleImporter {
}
private fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(), searchIn.toList())
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(false), searchIn.toList())
@Nested
inner class Constructor {
@ -243,7 +247,7 @@ class TestModuleImporter {
@Test
fun testWithNonExistingName() {
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
val errors = ErrorReporterForTests()
val errors = ErrorReporterForTests(false)
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
@ -252,14 +256,14 @@ class TestModuleImporter {
val result = importer.importLibraryModule(filenameNoExt)
assertThat(count[n] + " call / NO .p8 extension", result, Is(nullValue()))
assertFalse(errors.noErrors(), count[n] + " call / NO .p8 extension")
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist")
assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist")
errors.report()
assertThat(program.modules.size, equalTo(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")
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist.p8")
assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist.p8")
errors.report()
assertThat(program.modules.size, equalTo(1))
}

View File

@ -5,7 +5,7 @@ import org.junit.jupiter.api.TestInstance
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.compiler.target.C64Target
import prog8.optimizer.CallGraph
import prog8.compilerinterface.CallGraph
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.assertEquals
@ -26,11 +26,11 @@ class TestCallgraph {
}
"""
val result = compileText(C64Target, false, sourcecode).assertSuccess()
val graph = CallGraph(result.programAst)
val graph = CallGraph(result.program)
assertEquals(1, graph.imports.size)
assertEquals(1, graph.importedBy.size)
val toplevelModule = result.programAst.toplevelModule
val toplevelModule = result.program.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single()
assertEquals("string", importedModule.name)
val importedBy = graph.importedBy.getValue(importedModule).single()
@ -45,7 +45,7 @@ class TestCallgraph {
assertFalse(sub in graph.calls)
assertFalse(sub in graph.calledBy)
if(sub === result.programAst.entrypoint)
if(sub === result.program.entrypoint)
assertFalse(graph.unused(sub), "start() should always be marked as used to avoid having it removed")
else
assertTrue(graph.unused(sub))
@ -66,11 +66,11 @@ class TestCallgraph {
}
"""
val result = compileText(C64Target, false, sourcecode).assertSuccess()
val graph = CallGraph(result.programAst)
val graph = CallGraph(result.program)
assertEquals(1, graph.imports.size)
assertEquals(1, graph.importedBy.size)
val toplevelModule = result.programAst.toplevelModule
val toplevelModule = result.program.toplevelModule
val importedModule = graph.imports.getValue(toplevelModule).single()
assertEquals("string", importedModule.name)
val importedBy = graph.importedBy.getValue(importedModule).single()

View File

@ -7,11 +7,13 @@ import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
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
/**
@ -34,7 +36,7 @@ class TestCompilerOnCharLit {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
@ -58,7 +60,7 @@ class TestCompilerOnCharLit {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
@ -73,9 +75,11 @@ class TestCompilerOnCharLit {
// val initializerValue = decl.value as CharLiteral
// assertEquals('\n', (initializerValue as CharLiteral).value)
assertIs<NumericLiteralValue>(decl.value,
"char literal should have been replaced by ubyte literal")
val initializerValue = decl.value as NumericLiteralValue
assertNull(decl.value, "initializer value should have been moved to separate assignment")
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")
val initializerValue = assignInitialValue.value as NumericLiteralValue
assertEquals(DataType.UBYTE, initializerValue.type)
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort())
}
@ -93,7 +97,7 @@ class TestCompilerOnCharLit {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]

View File

@ -1,6 +1,5 @@
package prog8tests
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.DynamicTest.dynamicTest
import org.junit.jupiter.api.TestFactory
@ -8,8 +7,12 @@ import org.junit.jupiter.api.TestInstance
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import prog8tests.helpers.*
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.helpers.assertSuccess
import kotlin.io.path.absolute
import kotlin.io.path.exists
@ -39,8 +42,10 @@ class TestCompilerOnExamples {
compileProgram(
filepath,
optimize,
optimizeFloatExpressions = false,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = platform.name,
sourceDirs = listOf(),
outputDir

View File

@ -8,7 +8,10 @@ import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Label
import prog8.compiler.target.Cx16Target
import prog8tests.helpers.*
import prog8tests.ast.helpers.*
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
@ -34,7 +37,7 @@ class TestCompilerOnImportsAndIncludes {
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
.assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val strLits = startSub.statements
.filterIsInstance<FunctionCallStatement>()
@ -59,7 +62,7 @@ class TestCompilerOnImportsAndIncludes {
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
.assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val args = startSub.statements
.filterIsInstance<FunctionCallStatement>()
@ -105,8 +108,8 @@ class TestCompilerOnImportsAndIncludes {
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 == workinDir
// 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"

View File

@ -5,18 +5,21 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestFactory
import org.junit.jupiter.api.TestInstance
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.astprocessing.size
import prog8.compiler.astprocessing.toConstantIntegerRange
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.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import prog8tests.helpers.mapCombinations
import kotlin.test.assertContains
import kotlin.test.assertEquals
@ -40,7 +43,7 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val decl = startSub
.statements.filterIsInstance<VarDecl>()[0]
@ -69,7 +72,7 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val decl = startSub
.statements.filterIsInstance<VarDecl>()[0]
@ -151,7 +154,7 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val iterable = startSub
.statements.filterIsInstance<ForLoop>()
@ -162,10 +165,10 @@ class TestCompilerOnRanges {
val expectedEnd = platform.encodeString("f", false)[0].toInt()
val expectedStr = "$expectedStart .. $expectedEnd"
val intProgression = rangeExpr.toConstantIntegerRange(platform)
val intProgression = rangeExpr.toConstantIntegerRange()
val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
assertEquals(expectedStr, actualStr,".first .. .last")
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(platform), "rangeExpr.size()")
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(), "rangeExpr.size()")
}
@Test
@ -182,15 +185,15 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>()
.map { it.iterable }
.filterIsInstance<RangeExpr>()[0]
assertEquals(2, rangeExpr.size(platform))
val intProgression = rangeExpr.toConstantIntegerRange(platform)
assertEquals(2, rangeExpr.size())
val intProgression = rangeExpr.toConstantIntegerRange()
assertEquals(0, intProgression?.first)
assertEquals(1, intProgression?.last)
}
@ -209,21 +212,22 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>()
.map { it.iterable }
.filterIsInstance<RangeExpr>()[0]
assertEquals(9, rangeExpr.size(platform))
val intProgression = rangeExpr.toConstantIntegerRange(platform)
assertEquals(9, rangeExpr.size())
val intProgression = rangeExpr.toConstantIntegerRange()
assertEquals(1, intProgression?.first)
assertEquals(9, intProgression?.last)
}
@Test
fun testForLoopWithRange_str_downto_str() {
val errors = ErrorReporterForTests()
compileText(Cx16Target, true, """
main {
sub start() {
@ -233,8 +237,10 @@ class TestCompilerOnRanges {
}
}
}
""").assertFailure()
//TODO("test exact compile error(s)")
""", 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")
}
@Test
@ -250,7 +256,7 @@ class TestCompilerOnRanges {
}
""").assertSuccess()
val program = result.programAst
val program = result.program
val startSub = program.entrypoint
val iterable = startSub
.statements.filterIsInstance<ForLoop>()
@ -260,5 +266,15 @@ class TestCompilerOnRanges {
assertEquals(DataType.STR, iterable.inferType(program).getOr(DataType.UNDEFINED))
}
@Test
fun 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.toConstantIntegerRange()
}
}

View File

@ -6,7 +6,11 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.compiler.compileProgram
import prog8.compiler.target.Cx16Target
import prog8tests.helpers.*
import prog8tests.ast.helpers.assumeReadableFile
import prog8tests.ast.helpers.fixturesDir
import prog8tests.ast.helpers.outputDir
import prog8tests.ast.helpers.workingDir
import prog8tests.helpers.assertSuccess
import java.nio.file.Path
import kotlin.io.path.absolute
import kotlin.io.path.createTempFile
@ -43,8 +47,10 @@ class TestCompilerOptionSourcedirs {
compileProgram(
filepath = filePath,
optimize = false,
optimizeFloatExpressions = false,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = Cx16Target.name,
sourceDirs,
outputDir

View File

@ -3,14 +3,14 @@ package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.internedStringsModuleName
import prog8.compiler.ErrorReporter
import prog8.compiler.ZeropageType
import prog8.compiler.determineCompilationOptions
import prog8.compiler.parseImports
import prog8.compiler.target.C64Target
import prog8.compilerinterface.ZeropageType
import prog8tests.ast.helpers.outputDir
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import prog8tests.helpers.outputDir
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ -30,9 +30,9 @@ main {
}
}
""").assertSuccess()
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
val moduleNames = result.programAst.modules.map { it.name }
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",
@ -44,7 +44,7 @@ main {
"prog8_lib"
), moduleNames.drop(1), "module order in parse tree")
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
}
@Test
@ -61,8 +61,8 @@ main {
}
}
""").assertSuccess()
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
val options = determineCompilationOptions(result.programAst, C64Target)
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
val options = determineCompilationOptions(result.program, C64Target)
assertTrue(options.floats)
assertEquals(ZeropageType.DONTUSE, options.zeropage)
assertTrue(options.noSysInit)
@ -70,7 +70,7 @@ main {
@Test
fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() {
val errors = ErrorReporter()
val errors = ErrorReporterForTests()
val sourceText = """
%import textio
%import floats

View File

@ -12,11 +12,12 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.PrefixExpression
import prog8.ast.statements.*
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.C64Target
import prog8.compilerinterface.isInRegularRAMof
import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@ -70,7 +71,7 @@ class TestMemory {
@Test
fun testInValidRamC64_memory_identifiers() {
val program = Program("test", DummyFunctions, DummyMemsizer)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -89,7 +90,7 @@ class TestMemory {
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
module.linkIntoProgram(program)
return target
@ -107,9 +108,9 @@ class TestMemory {
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)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -121,9 +122,9 @@ class TestMemory {
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)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -135,9 +136,9 @@ class TestMemory {
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)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine))
@ -149,9 +150,9 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -164,9 +165,9 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertTrue(target.isInRegularRAMof(C64Target.machine))
@ -179,9 +180,9 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
assertFalse(target.isInRegularRAMof(C64Target.machine))

View File

@ -6,10 +6,10 @@ import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.toHex
import prog8.compiler.CompilerException
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
@ -81,10 +81,10 @@ class TestNumbers {
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<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
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

View File

@ -1,18 +1,21 @@
package prog8tests
import org.hamcrest.CoreMatchers.instanceOf
import org.hamcrest.MatcherAssert.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.statements.Block
import prog8.ast.statements.Return
import prog8.ast.statements.Subroutine
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.statements.*
import prog8.compiler.target.C64Target
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.assertEquals
import kotlin.test.assertSame
import kotlin.test.assertTrue
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestOptimization {
@ -28,10 +31,10 @@ class TestOptimization {
}
"""
val result = compileText(C64Target, true, sourcecode).assertSuccess()
val toplevelModule = result.programAst.toplevelModule
val toplevelModule = result.program.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements.single() as Subroutine
assertSame(result.programAst.entrypoint, startSub)
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")
}
@ -50,13 +53,71 @@ class TestOptimization {
}
"""
val result = compileText(C64Target, true, sourcecode).assertSuccess()
val toplevelModule = result.programAst.toplevelModule
val toplevelModule = result.program.toplevelModule
val mainBlock = toplevelModule.statements.single() as Block
val startSub = mainBlock.statements[0] as Subroutine
val emptySub = mainBlock.statements[1] as Subroutine
assertSame(result.programAst.entrypoint, startSub)
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")
}
@Test
fun 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)
val constvalue = tc.constValue(program)!!
assertIs<NumericLiteralValue>(constvalue)
assertEquals(11, constvalue.number.toInt())
assertEquals(DataType.BYTE, constvalue.type)
assertSame(tc, constvalue.parent)
}
@Test
fun 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
}
}
"""
val result = compileText(C64Target, true, sourcecode).assertSuccess()
val mainsub = result.program.entrypoint
assertEquals(10, mainsub.statements.size)
val declTest = mainsub.statements[0] as VarDecl
val declX1 = mainsub.statements[1] as VarDecl
val initX1 = mainsub.statements[2] as Assignment
val declX2 = mainsub.statements[3] as VarDecl
val initX2 = mainsub.statements[4] as Assignment
val declY1 = mainsub.statements[5] as VarDecl
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())
}
}

View File

@ -1,16 +1,17 @@
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 prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.target.cbm.Petscii
import kotlin.test.*
import java.io.CharConversionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -34,8 +35,6 @@ class TestPetscii {
assertThat("expect lowercase error fallback", Petscii.encodePetscii("", true), equalTo(Ok(listOf<Short>(0xd3))))
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256), true) }
}
@Test
@ -48,8 +47,6 @@ class TestPetscii {
assertThat("expecting fallback", Petscii.encodePetscii(""), equalTo(Ok(listOf<Short>(250))))
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256)) }
}
@Test
@ -62,8 +59,6 @@ class TestPetscii {
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(Ok(listOf<Short>(94))))
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256), true) }
}
@Test
@ -77,33 +72,129 @@ class TestPetscii {
assertThat("expecting fallback", Petscii.encodeScreencode(""), equalTo(Ok(listOf<Short>(122))))
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256)) }
}
@Test
fun testLiteralValueComparisons() {
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
assertTrue(ten != nine)
fun 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" }
assertTrue(ten > nine)
assertTrue(ten >= nine)
assertTrue(ten >= ten)
assertFalse(ten > ten)
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) }
assertFalse(ten < nine)
assertFalse(ten <= nine)
assertTrue(ten <= ten)
assertFalse(ten < ten)
val abc = StringLiteralValue("abc", false, Position.DUMMY)
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
Petscii.scr2petscii(-1).expectError { "-1 should error" }
Petscii.scr2petscii(256).expectError { "256 should error" }
Petscii.petscii2scr(-1, true).expectError { "-1 should error" }
Petscii.petscii2scr(256, true).expectError { "256 should error" }
Petscii.petscii2scr(-1, false).expectError { "-1 should error" }
Petscii.petscii2scr(256, false).expectError { "256 should error" }
}
@Test
fun testSpecialReplacements()
{
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()
Petscii.encodePetscii("`", false).expectError { "shouldn't have translation for backtick" }
Petscii.encodePetscii("`", true).expectError { "shouldn't have translation for backtick" }
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))
}
@Test
fun 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))
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
assertEquals(221, encodeP('│', false))
assertEquals(221, encodeP('│', true))
assertEquals(93, encodeS('│', false))
assertEquals(93, encodeS('│', true))
// underscore
assertEquals(228, encodeP('_', false))
assertEquals(228, encodeP('_', true))
assertEquals(100, encodeS('_', false))
assertEquals(100, encodeS('_', true))
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
assertEquals(228, encodeP('▁', false))
assertEquals(228, encodeP('▁', true))
assertEquals(100, encodeS('▁', false))
assertEquals(100, encodeS('▁', true))
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
assertEquals(192, encodeP('─', false))
assertEquals(192, encodeP('─', true))
assertEquals(64, encodeS('─', false))
assertEquals(64, encodeS('─', true))
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
assertEquals(221, encodeP('│', false))
assertEquals(221, encodeP('│', true))
assertEquals(93, encodeS('│', false))
assertEquals(93, encodeS('│', true))
}
@Test
fun 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())
// │ 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())
}
}

View File

@ -0,0 +1,126 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.GlobalNamespace
import prog8.ast.base.ParentSentinel
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestScoping {
@Test
fun testModulesParentIsGlobalNamespace() {
val src = """
main {
sub start() {
}
}
"""
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)
}
@Test
fun testAnonScopeVarsMovedIntoSubroutineScope() {
val src = """
main {
sub start() {
repeat {
ubyte xx = 99
xx++
}
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
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")
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")
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)
}
@Test
fun testLabelsWithAnonScopes() {
val src = """
main {
sub start() {
uword addr
goto labeloutside
if true {
if true {
addr = &iflabel
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto labeloutside
goto iflabel
goto main.start.nested.nestedlabel
goto nested.nestedlabel
}
iflabel:
}
repeat {
addr = &iflabel
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto iflabel
goto labelinside
goto main.start.nested.nestedlabel
goto nested.nestedlabel
labelinside:
}
sub nested () {
nestedlabel:
addr = &nestedlabel
goto nestedlabel
goto main.start.nested.nestedlabel
}
labeloutside:
addr = &iflabel
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto main.start.nested.nestedlabel
goto nested.nestedlabel
}
}
"""
val result = compileText(C64Target, false, src, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
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")
}
}

View File

@ -0,0 +1,255 @@
package prog8tests
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestSubroutines {
@Test
fun stringParameter() {
val text = """
main {
sub start() {
str text = "test"
asmfunc("text")
asmfunc(text)
asmfunc($2000)
func("text")
func(text)
func($2000)
}
asmsub asmfunc(str thing @AY) {
}
sub func(str thing) {
uword t2 = thing as uword
asmfunc(thing)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
val mainBlock = module.statements.single() as Block
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
assertTrue(asmfunc.isAsmSubroutine)
assertEquals(DataType.STR, asmfunc.parameters.single().type)
assertTrue(asmfunc.statements.isEmpty())
assertFalse(func.isAsmSubroutine)
assertEquals(DataType.STR, func.parameters.single().type)
assertEquals(4, func.statements.size)
val paramvar = func.statements[0] as VarDecl
assertEquals("thing", paramvar.name)
assertEquals(DataType.STR, paramvar.datatype)
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)
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())
}
@Test
fun stringParameterAsmGen() {
val text = """
main {
sub start() {
str text = "test"
asmfunc("text")
asmfunc(text)
asmfunc($2000)
func("text")
func(text)
func($2000)
}
asmsub asmfunc(str thing @AY) {
}
sub func(str thing) {
uword t2 = thing as uword
asmfunc(thing)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
val module = result.program.toplevelModule
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)
assertEquals(5, func.statements.size)
assertTrue(func.statements[4] is 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")
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())
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())
}
@Test
fun arrayParameterNotYetAllowed_ButShouldPerhapsBe() {
// note: the *parser* accepts this as it is valid *syntax*,
// however, it's not (yet) valid for the compiler
val text = """
main {
sub start() {
}
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
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")
}
@Test
@Disabled("TODO: allow array parameter in signature") // TODO allow this?
fun arrayParameter() {
val text = """
main {
sub start() {
ubyte[] array = [1,2,3]
asmfunc(array)
asmfunc([4,5,6])
asmfunc($2000)
asmfunc(12.345)
func(array)
func([4,5,6])
func($2000)
func(12.345)
}
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
val module = result.program.toplevelModule
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())
}
@Test
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsDirectMemoryRead() {
val text="""
main {
sub thing(uword rr) {
ubyte xx = rr[1] ; should still work as var initializer that will be rewritten
ubyte yy
yy = rr[2]
uword other
ubyte zz = other[3]
}
sub start() {
ubyte[] array=[1,2,3]
thing(array)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
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")
val xx = thing.statements[1] as VarDecl
assertNull(xx.value, "vardecl init values must have been moved to separate assignments")
val assignXX = thing.statements[2] as Assignment
val assignYY = thing.statements[4] as Assignment
val assignZZ = thing.statements[8] as Assignment
assertEquals(listOf("xx"), assignXX.target.identifier!!.nameInSource)
assertEquals(listOf("yy"), assignYY.target.identifier!!.nameInSource)
assertEquals(listOf("zz"), assignZZ.target.identifier!!.nameInSource)
val 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())
}
@Test
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsMemoryWrite() {
val text="""
main {
sub thing(uword rr) {
rr[10] = 42
}
sub start() {
ubyte[] array=[1,2,3]
thing(array)
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
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")
val assignRR = thing.statements[1] as Assignment
assertEquals(42, (assignRR.value as NumericLiteralValue).number.toInt())
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())
}
}

View File

@ -3,21 +3,77 @@ package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import prog8.compilerinterface.*
import prog8tests.helpers.ErrorReporterForTests
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestAbstractZeropage {
@Test
fun testAbstractZeropage() {
val compTarget = DummyCompilationTarget()
val zp = DummyZeropage(
CompilationOptions(
OutputType.RAW,
LauncherType.NONE,
ZeropageType.FULL,
listOf((0x50..0x5f)),
false,
false,
compTarget
)
)
assertEquals(256-6-16, zp.free.size)
}
class DummyCompilationTarget: ICompilationTarget {
override val name: String = "dummy"
override val machine: IMachineDefinition
get() = throw NotImplementedError("dummy")
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
throw NotImplementedError("dummy")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
throw NotImplementedError("dummy")
}
override fun memorySize(dt: DataType): Int {
throw NotImplementedError("dummy")
}
}
class DummyZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1: Int = 0x10
override val SCRATCH_REG: Int = 0x11
override val SCRATCH_W1: Int= 0x20
override val SCRATCH_W2: Int = 0x30
init {
free.addAll(0..255)
removeReservedFromFreePool()
}
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestC64Zeropage {
private val errors = ErrorReporter()
private val errors = ErrorReporterForTests()
@Test
fun testNames() {
@ -35,11 +91,11 @@ class TestC64Zeropage {
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> {
assertFailsWith<InternalCompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
assertFailsWith<InternalCompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
@ -54,10 +110,10 @@ class TestC64Zeropage {
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<CompilerException> {
assertFailsWith<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
}
assertFailsWith<CompilerException> {
assertFailsWith<InternalCompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
}
}
@ -67,7 +123,7 @@ class TestC64Zeropage {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free)
assertEquals(0, zp.availableBytes())
assertFailsWith<CompilerException> {
assertFailsWith<InternalCompilerException> {
zp.allocate("", DataType.BYTE, null, errors)
}
}
@ -216,7 +272,7 @@ class TestC64Zeropage {
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage {
private val errors = ErrorReporter()
private val errors = ErrorReporterForTests()
@Test
fun testReservedLocations() {

View File

@ -1,21 +1,36 @@
package prog8tests.helpers
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
val DummyFunctions = object : IBuiltinFunctions {
internal val DummyFunctions = object : IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(
name: String,
args: List<Expression>,
position: Position,
memsizer: IMemSizer
): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return ""
}
}

View File

@ -1,25 +1,29 @@
package prog8tests.helpers
import prog8.ast.base.Position
import prog8.compiler.IErrorReporter
import prog8.compilerinterface.IErrorReporter
class ErrorReporterForTests: IErrorReporter {
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
override fun err(msg: String, position: Position) {
errors.add(msg)
errors.add("${position.toClickableStr()} $msg")
}
override fun warn(msg: String, position: Position) {
warnings.add(msg)
warnings.add("${position.toClickableStr()} $msg")
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun report() {
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
errors.clear()
warnings.clear()
}

View File

@ -2,7 +2,10 @@ package prog8tests.helpers
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.ICompilationTarget
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8tests.ast.helpers.assumeReadableFile
import prog8tests.ast.helpers.outputDir
import java.nio.file.Path
import kotlin.io.path.name
import kotlin.test.assertFalse
@ -28,18 +31,23 @@ internal fun compileFile(
optimize: Boolean,
fileDir: Path,
fileName: String,
outputDir: Path = prog8tests.helpers.outputDir
outputDir: Path = prog8tests.ast.helpers.outputDir,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true
) : CompilationResult {
val filepath = fileDir.resolve(fileName)
assumeReadableFile(filepath)
return compileProgram(
filepath,
optimize,
writeAssembly = true,
optimizeFloatExpressions = false,
writeAssembly = writeAssembly,
slowCodegenWarnings = false,
quietAssembler = true,
platform.name,
sourceDirs = listOf(),
outputDir
outputDir,
errors = errors ?: ErrorReporterForTests()
)
}
@ -51,10 +59,12 @@ internal fun compileFile(
internal fun compileText(
platform: ICompilationTarget,
optimize: Boolean,
sourceText: String
sourceText: String,
errors: IErrorReporter? = null,
writeAssembly: 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)
return compileFile(platform, optimize, filePath.parent, filePath.name, errors=errors, writeAssembly=writeAssembly)
}

View File

@ -3,14 +3,13 @@ plugins {
id "org.jetbrains.kotlin.jvm"
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenCentral()
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
dependencies {
implementation 'org.antlr:antlr4-runtime:4.9.2'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
@ -26,22 +25,6 @@ configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
sourceSets {
main {
java {

View File

@ -12,9 +12,8 @@ import prog8.ast.walk.IAstVisitor
/**
* Produces Prog8 source text from a [Program] (AST node),
* passing it as a String to the specified receiver function.
* TODO: rename/refactor to make proper sense in the presence of class [prog8.parser.SourceCode]
*/
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
private var scopelevel = 0
private fun indent(s: String) = " ".repeat(scopelevel) + s
@ -98,7 +97,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(decl: VarDecl) {
// if the vardecl is a parameter of a subroutine, don't output it again
val paramNames = (decl.definingScope as? Subroutine)?.parameters?.map { it.name }
val paramNames = decl.definingSubroutine?.parameters?.map { it.name }
if(paramNames!=null && decl.name in paramNames)
return

View File

@ -3,7 +3,6 @@ package prog8.ast
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
@ -21,117 +20,12 @@ interface IFunctionCall {
var args: MutableList<Expression>
}
interface INameScope {
val name: String
interface IStatementContainer {
val position: Position
val statements: MutableList<Statement>
val parent: Node
val statements: MutableList<Statement>
fun linkParents(parent: Node)
fun subScope(name: String): INameScope? {
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> if(stmt.body.name==name) return stmt.body
is UntilLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> {
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars && stmt.elsepart.name==name) return stmt.elsepart
}
is IfStatement -> {
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars && stmt.elsepart.name==name) return stmt.elsepart
}
is WhenStatement -> {
val scope = stmt.choices.firstOrNull { it.statements.name==name }
if(scope!=null)
return scope.statements
}
is INameScope -> if(stmt.name==name) return stmt
else -> {}
}
}
return null
}
fun getLabelOrVariable(name: String): Statement? {
// this is called A LOT and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
if (stmt is AnonymousScope) {
val sub = stmt.getLabelOrVariable(name)
if(sub!=null)
return sub
}
}
return null
}
val allDefinedSymbols: List<Pair<String, Statement>>
get() {
return statements.mapNotNull {
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
}
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
if(scopedName.size>1) {
// a scoped name refers to a name in another module.
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
for(module in localContext.definingModule.program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScope(name)
if(scope==null)
break
}
if(scope!=null) {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
} else {
// unqualified name
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.getLabelOrVariable(scopedName[0])
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
}
}
val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" }
val containsNoCodeNorVars get() = !containsCodeOrVars
fun remove(stmt: Statement) {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
@ -140,11 +34,11 @@ interface INameScope {
fun getAllLabels(label: String): List<Label> {
val result = mutableListOf<Label>()
fun find(scope: INameScope) {
fun find(scope: IStatementContainer) {
scope.statements.forEach {
when(it) {
is Label -> result.add(it)
is INameScope -> find(it)
is IStatementContainer -> find(it)
is IfStatement -> {
find(it.truepart)
find(it.elsepart)
@ -162,22 +56,6 @@ interface INameScope {
return result
}
fun nextSibling(stmt: Statement): Statement? {
val nextIdx = statements.indexOfFirst { it===stmt } + 1
return if(nextIdx < statements.size)
statements[nextIdx]
else
null
}
fun previousSibling(stmt: Statement): Statement? {
val previousIdx = statements.indexOfFirst { it===stmt } - 1
return if(previousIdx>=0)
statements[previousIdx]
else
null
}
fun indexOfChild(stmt: Statement): Int {
val idx = statements.indexOfFirst { it===stmt }
if(idx>=0)
@ -185,6 +63,159 @@ interface INameScope {
else
throw FatalAstException("attempt to find a non-child")
}
fun isEmpty(): Boolean = statements.isEmpty()
fun isNotEmpty(): Boolean = statements.isNotEmpty()
fun searchSymbol(name: String): Statement? {
// this is called quite a lot and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference...
for (stmt in statements) {
when(stmt) {
// is INamedStatement -> {
// if(stmt.name==name) return stmt
// }
is VarDecl -> {
if(stmt.name==name) return stmt
}
is Label -> {
if(stmt.name==name) return stmt
}
is Subroutine -> {
if(stmt.name==name)
return stmt
}
is AnonymousScope -> {
val found = stmt.searchSymbol(name)
if(found!=null)
return found
}
is IfStatement -> {
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
if(found!=null)
return found
}
is BranchStatement -> {
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
if(found!=null)
return found
}
is ForLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is WhileLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is RepeatLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is UntilLoop -> {
val found = stmt.body.searchSymbol(name)
if(found!=null)
return found
}
is WhenStatement -> {
stmt.choices.forEach {
val found = it.statements.searchSymbol(name)
if(found!=null)
return found
}
}
else -> {
// NOTE: when other nodes containing AnonymousScope are introduced,
// these should be added here as well to look into!
}
}
}
return null
}
val allDefinedSymbols: List<Pair<String, Statement>>
get() {
return statements.filterIsInstance<INamedStatement>().map { Pair(it.name, it as Statement) }
}
}
interface INameScope: IStatementContainer, INamedStatement {
fun subScope(name: String): INameScope? = statements.firstOrNull { it is INameScope && it.name==name } as? INameScope
fun lookup(scopedName: List<String>) : Statement? {
return if(scopedName.size>1)
lookupQualified(scopedName)
else {
lookupUnqualified(scopedName[0])
}
}
private fun lookupQualified(scopedName: List<String>): Statement? {
// a scoped name refers to a name in another namespace.
// look "up" from our current scope to search for the correct one.
val localScope = this.subScope(scopedName[0])
if(localScope!=null)
return resolveLocally(localScope, scopedName.drop(1))
var statementScope = this
while(statementScope !is GlobalNamespace) {
if(statementScope !is Module && statementScope.name==scopedName[0]) {
return resolveLocally(statementScope, scopedName.drop(1))
} else {
statementScope = (statementScope as Node).definingScope
}
}
// not found, try again but now assume it's a globally scoped name starting with the name of a block
for(module in (this as Node).definingModule.program.modules) {
module.statements.forEach {
if(it is Block && it.name==scopedName[0])
return it.lookup(scopedName)
}
}
return null
}
private fun resolveLocally(startScope: INameScope, name: List<String>): Statement? {
var scope: INameScope? = startScope
for(part in name.dropLast(1)) {
scope = scope!!.subScope(part)
if(scope==null)
return null
}
return scope!!.searchSymbol(name.last())
}
private fun lookupUnqualified(name: String): Statement? {
val builtinFunctionsNames = (this as Node).definingModule.program.builtinFunctions.names
if(name in builtinFunctionsNames) {
// builtin functions always exist, return a dummy placeholder for them
val builtinPlaceholder = Label("builtin::$name", this.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
// search for the unqualified name in the current scope (and possibly in any anonymousscopes it may contain)
// if it's not found there, jump up one higher in the namespaces and try again.
var statementScope = this
while(statementScope !is GlobalNamespace) {
val symbol = statementScope.searchSymbol(name)
if(symbol!=null)
return symbol
else
statementScope = (statementScope as Node).definingScope
}
return null
}
// private fun getNamedSymbol(name: String): Statement? =
// statements.singleOrNull { it is INamedStatement && it.name==name }
val containsCodeOrVars get() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm" }
val containsNoCodeNorVars get() = !containsCodeOrVars
}
@ -234,119 +265,6 @@ interface Node {
}
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String,
val builtinFunctions: IBuiltinFunctions,
val memsizer: IMemSizer): Node {
private val _modules = mutableListOf<Module>()
val modules: List<Module> = _modules
val namespace: GlobalNamespace = GlobalNamespace(modules, builtinFunctions.names)
init {
// insert a container module for all interned strings later
val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(internedStringsModuleName))
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
internedStringsModule.statements.add(block)
_modules.add(0, internedStringsModule)
internedStringsModule.linkParents(namespace)
internedStringsModule.program = this
}
fun addModule(module: Module): Program {
require(null == _modules.firstOrNull { it.name == module.name })
{ "module '${module.name}' already present" }
_modules.add(module)
module.linkIntoProgram(this)
return this
}
fun removeModule(module: Module) = _modules.remove(module)
fun moveModuleToFront(module: Module): Program {
require(_modules.contains(module))
{ "Not a module of this program: '${module.name}'"}
_modules.remove(module)
_modules.add(0, module)
return this
}
val allBlocks: List<Block>
get() = modules.flatMap { it.statements.filterIsInstance<Block>() }
val entrypoint: Subroutine
get() {
val mainBlocks = allBlocks.filter { it.name == "main" }
return when (mainBlocks.size) {
0 -> throw FatalAstException("no 'main' block")
1 -> mainBlocks[0].subScope("start") as Subroutine
else -> throw FatalAstException("more than one 'main' block")
}
}
val toplevelModule: Module
get() = modules.first { it.name!=internedStringsModuleName }
val definedLoadAddress: Int
get() = toplevelModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
fun internString(string: StringLiteralValue): List<String> {
// Move a string literal into the internal, deduplicated, string pool
// replace it with a variable declaration that points to the entry in the pool.
if(string.parent is VarDecl) {
// deduplication can only be performed safely for known-const strings (=string literals OUTSIDE OF A VARDECL)!
throw FatalAstException("cannot intern a string literal that's part of a vardecl")
}
fun getScopedName(string: StringLiteralValue): List<String> {
val internedStringsBlock = modules
.first { it.name == internedStringsModuleName }.statements
.first { it is Block && it.name == internedStringsModuleName } as Block
val varName = "string_${internedStringsBlock.statements.size}"
val decl = VarDecl(
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, position = string.position
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)
return listOf(internedStringsModuleName, decl.name)
}
val key = Pair(string.value, string.altEncoding)
val existing = internedStringsUnique[key]
if (existing != null)
return existing
val scopedName = getScopedName(string)
internedStringsUnique[key] = scopedName
return scopedName
}
override val position: Position = Position.DUMMY
override var parent: Node
get() = throw FatalAstException("program has no parent")
set(_) = throw FatalAstException("can't set parent of program")
override fun linkParents(parent: Node) {
modules.forEach {
it.linkParents(namespace)
}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Module && replacement is Module)
val idx = _modules.indexOfFirst { it===node }
_modules[idx] = replacement
replacement.linkIntoProgram(this)
}
}
open class Module(final override var statements: MutableList<Statement>,
final override val position: Position,
val source: SourceCode) : Node, INameScope {
@ -393,12 +311,16 @@ open class Module(final override var statements: MutableList<Statement>,
}
class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunctionNames: Set<String>): Node, INameScope {
class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>() // not used
override var parent: Node = ParentSentinel
override fun lookup(scopedName: List<String>): Statement? {
throw NotImplementedError("use lookup on actual ast node instead")
}
override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(this) }
}
@ -406,29 +328,6 @@ class GlobalNamespace(val modules: Iterable<Module>, private val builtinFunction
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("cannot replace anything in the namespace")
}
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
if (scopedName.size == 1 && scopedName[0] in builtinFunctionNames) {
// builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.lookup(scopedName, localContext)
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// lookup something from the module.
return when (val stmt = localContext.definingModule.lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
else -> throw SyntaxError("invalid identifier target type", stmt.position)
}
}
}
object BuiltinFunctionScopePlaceholder : INameScope {

View File

@ -8,6 +8,6 @@ import prog8.ast.expressions.NumericLiteralValue
interface IBuiltinFunctions {
val names: Set<String>
val purefunctionNames: Set<String>
fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue?
fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue?
fun returnType(name: String, args: MutableList<Expression>): InferredTypes.InferredType
}
}

View File

@ -1,7 +0,0 @@
package prog8.ast
import prog8.ast.base.DataType
interface IMemSizer {
fun memorySize(dt: DataType): Int
}

View File

@ -0,0 +1,112 @@
package prog8.ast
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
import prog8.parser.SourceCode
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String,
val builtinFunctions: IBuiltinFunctions,
val memsizer: IMemSizer,
val encoding: IStringEncoding
) {
private val _modules = mutableListOf<Module>()
val modules: List<Module> = _modules
val namespace: GlobalNamespace = GlobalNamespace(modules)
init {
// insert a container module for all interned strings later
val internedStringsModule =
Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(internedStringsModuleName))
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
internedStringsModule.statements.add(block)
_modules.add(0, internedStringsModule)
internedStringsModule.linkParents(namespace)
internedStringsModule.program = this
}
fun addModule(module: Module): Program {
require(null == _modules.firstOrNull { it.name == module.name })
{ "module '${module.name}' already present" }
_modules.add(module)
module.linkIntoProgram(this)
return this
}
fun removeModule(module: Module) = _modules.remove(module)
fun moveModuleToFront(module: Module): Program {
require(_modules.contains(module))
{ "Not a module of this program: '${module.name}'"}
_modules.remove(module)
_modules.add(0, module)
return this
}
val allBlocks: List<Block>
get() = modules.flatMap { it.statements.filterIsInstance<Block>() }
val entrypoint: Subroutine
get() {
val mainBlocks = allBlocks.filter { it.name == "main" }
return when (mainBlocks.size) {
0 -> throw FatalAstException("no 'main' block")
1 -> mainBlocks[0].subScope("start") as Subroutine
else -> throw FatalAstException("more than one 'main' block")
}
}
val toplevelModule: Module
get() = modules.first { it.name!= internedStringsModuleName }
val definedLoadAddress: Int
get() = toplevelModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
fun internString(string: StringLiteralValue): List<String> {
// Move a string literal into the internal, deduplicated, string pool
// replace it with a variable declaration that points to the entry in the pool.
if(string.parent is VarDecl) {
// deduplication can only be performed safely for known-const strings (=string literals OUTSIDE OF A VARDECL)!
throw FatalAstException("cannot intern a string literal that's part of a vardecl")
}
fun getScopedName(string: StringLiteralValue): List<String> {
val internedStringsBlock = modules
.first { it.name == internedStringsModuleName }.statements
.first { it is Block && it.name == internedStringsModuleName } as Block
val varName = "string_${internedStringsBlock.statements.size}"
val decl = VarDecl(
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, position = string.position
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)
return listOf(internedStringsModuleName, decl.name)
}
val key = Pair(string.value, string.altEncoding)
val existing = internedStringsUnique[key]
if (existing != null)
return existing
val scopedName = getScopedName(string)
internedStringsUnique[key] = scopedName
return scopedName
}
}

View File

@ -29,7 +29,7 @@ private fun ParserRuleContext.toPosition() : Position {
pathString
}
// note: beware of TAB characters in the source text, they count as 1 column...
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
return Position(filename, start.line, start.charPositionInLine, start.charPositionInLine + start.stopIndex - start.startIndex)
}
internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
@ -186,7 +186,7 @@ private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
val inline = this.inline()!=null
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, inline, statements, toPosition())
}
@ -194,7 +194,7 @@ private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
private fun Prog8ANTLRParser.RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toInt()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
)
@ -252,7 +252,9 @@ private fun Prog8ANTLRParser.Asmsub_returnsContext.toAst(): List<AsmSubroutineRe
private fun Prog8ANTLRParser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map {
val vardecl = it.vardecl()
val datatype = vardecl.datatype()?.toAst() ?: DataType.UNDEFINED
var datatype = vardecl.datatype()?.toAst() ?: DataType.UNDEFINED
if(vardecl.ARRAYSIG()!=null || vardecl.arrayindex()!=null)
datatype = ElementToArrayTypes.getValue(datatype)
val register = it.register().text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
@ -304,7 +306,7 @@ private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
val inline = inline()!=null
val returntypes = sub_return_part()?.toAst() ?: emptyList()
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
sub_params()?.toAst()?.toMutableList() ?: mutableListOf(),
returntypes,
statement_block()?.toAst() ?: mutableListOf(),
inline,
@ -318,7 +320,9 @@ private fun Prog8ANTLRParser.Sub_return_partContext.toAst(): List<DataType> {
private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
val datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
var datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
if(it.ARRAYSIG()!=null || it.arrayindex()!=null)
datatype = ElementToArrayTypes.getValue(datatype)
SubroutineParameter(it.varname.text, datatype, it.toPosition())
}

View File

@ -6,6 +6,7 @@ import prog8.ast.base.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.IMemSizer
import java.util.*
@ -436,9 +437,18 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
fun linkParent(parent: Node) {
value?.linkParents(parent)
}
}
fun cast(targettype: DataType): CastValue {
val result = internalCast(targettype)
result.linkParent(this.parent)
return result
}
private fun internalCast(targettype: DataType): CastValue {
if(type==targettype)
return CastValue(true, this)
val numval = number.toDouble()
@ -513,7 +523,10 @@ class CharLiteral(val value: Char,
}
override fun referencesIdentifier(vararg scopedName: String) = false
override fun constValue(program: Program): NumericLiteralValue? = null // TODO: CharLiteral.constValue can't be NumericLiteralValue...
override fun constValue(program: Program): NumericLiteralValue {
val bytevalue = program.encoding.encodeString(value.toString(), altEncoding).single()
return NumericLiteralValue(DataType.UBYTE, bytevalue, position)
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -730,7 +743,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names)
BuiltinFunctionStatementPlaceholder(nameInSource[0], position, parent)
else
program.namespace.lookup(nameInSource, this)
definingScope.lookup(nameInSource)
fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl
fun targetSubroutine(program: Program): Subroutine? = targetStatement(program) as? Subroutine
@ -747,8 +760,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
}
override fun constValue(program: Program): NumericLiteralValue? {
val node = program.namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this)
val node = definingScope.lookup(nameInSource) ?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl
if(vardecl==null) {
return null
@ -816,7 +828,7 @@ class FunctionCall(override var target: IdentifierReference,
// lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1)
return null
val resultValue: NumericLiteralValue? = program.builtinFunctions.constValue(target.nameInSource[0], args, position, program.memsizer)
val resultValue: NumericLiteralValue? = program.builtinFunctions.constValue(target.nameInSource[0], args, position)
if(withDatatypeCheck) {
val resultDt = this.inferType(program)
if(resultValue==null || resultDt istype resultValue.type)

View File

@ -7,7 +7,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
interface ISymbolStatement {
interface INamedStatement {
val name: String
}
@ -32,6 +32,24 @@ sealed class Statement : Node {
scope.add(name)
return scope.joinToString(".")
}
fun nextSibling(): Statement? {
val statements = (parent as? IStatementContainer)?.statements ?: return null
val nextIdx = statements.indexOfFirst { it===this } + 1
return if(nextIdx < statements.size)
statements[nextIdx]
else
null
}
fun previousSibling(): Statement? {
val statements = (parent as? IStatementContainer)?.statements ?: return null
val previousIdx = statements.indexOfFirst { it===this } - 1
return if(previousIdx >= 0)
statements[previousIdx]
else
null
}
}
@ -52,7 +70,7 @@ class Block(override val name: String,
val address: Int?,
override var statements: MutableList<Statement>,
val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope, ISymbolStatement {
override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@ -99,7 +117,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
data class Label(override val name: String, override val position: Position) : Statement(), ISymbolStatement {
data class Label(override val name: String, override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@ -166,7 +184,7 @@ open class VarDecl(val type: VarDeclType,
val isArray: Boolean,
val autogeneratedDontRemove: Boolean,
val sharedWithAsm: Boolean,
final override val position: Position) : Statement(), ISymbolStatement {
final override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
var allowInitializeWithZero = true
@ -386,7 +404,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
fun inferType(program: Program): InferredTypes.InferredType {
if (identifier != null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, this) ?: return InferredTypes.unknown()
val symbol = definingScope.lookup(identifier!!.nameInSource) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
}
@ -538,19 +556,9 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
}
class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() { // TODO this isn't really a namescope...(names are scoped to the subroutine level)
override val name: String = "<anon-$sequenceNumber>"
override val position: Position) : IStatementContainer, Statement() {
override lateinit var parent: Node
companion object {
private var sequenceNumber = 1
}
init {
// make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++
}
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach { it.linkParents(this) }
@ -597,7 +605,7 @@ class AsmGenInfo {
// and also the predefined/ROM/register-based subroutines.
// (multiple return types can only occur for the latter type)
class Subroutine(override val name: String,
val parameters: List<SubroutineParameter>,
val parameters: MutableList<SubroutineParameter>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
@ -606,9 +614,9 @@ class Subroutine(override val name: String,
val isAsmSubroutine: Boolean,
val inline: Boolean,
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope, ISymbolStatement {
override val position: Position) : Statement(), INameScope {
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position)
constructor(name: String, parameters: MutableList<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position)
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position)
companion object {
@ -635,10 +643,19 @@ class Subroutine(override val name: String,
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement
replacement.parent = this
when(replacement) {
is SubroutineParameter -> {
val idx = parameters.indexOf(node)
parameters[idx] = replacement
replacement.parent = this
}
is Statement -> {
val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement
replacement.parent = this
}
else -> throw FatalAstException("can't replace")
}
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)

View File

@ -2,6 +2,7 @@ package prog8.ast.walk
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.base.ParentSentinel
import prog8.ast.expressions.*
import prog8.ast.statements.*
@ -9,7 +10,7 @@ import prog8.ast.statements.*
interface IAstModification {
fun perform()
class Remove(val node: Node, val parent: INameScope) : IAstModification {
class Remove(val node: Node, val parent: IStatementContainer) : IAstModification {
override fun perform() {
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
@ -24,21 +25,21 @@ interface IAstModification {
}
}
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
class InsertFirst(private val stmt: Statement, private val parent: IStatementContainer) : IAstModification {
override fun perform() {
parent.statements.add(0, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
class InsertLast(private val stmt: Statement, private val parent: IStatementContainer) : IAstModification {
override fun perform() {
parent.statements.add(stmt)
stmt.linkParents(parent as Node)
}
}
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) :
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: IStatementContainer) :
IAstModification {
override fun perform() {
val idx = parent.statements.indexOfFirst { it===after } + 1
@ -47,7 +48,7 @@ interface IAstModification {
}
}
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) :
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: IStatementContainer) :
IAstModification {
override fun perform() {
val idx = parent.statements.indexOfFirst { it===before }
@ -105,7 +106,7 @@ abstract class AstWalker {
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
open fun before(program: Program, parent: Node): Iterable<IAstModification> = noModifications
open fun before(program: Program): Iterable<IAstModification> = noModifications
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
@ -146,7 +147,7 @@ abstract class AstWalker {
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
open fun after(program: Program, parent: Node): Iterable<IAstModification> = noModifications
open fun after(program: Program): Iterable<IAstModification> = noModifications
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
@ -196,9 +197,9 @@ abstract class AstWalker {
}
fun visit(program: Program) {
track(before(program, program), program, program)
program.modules.forEach { it.accept(this, program) }
track(after(program, program), program, program)
track(before(program), ParentSentinel, program.namespace)
program.modules.forEach { it.accept(this, program.namespace) }
track(after(program), ParentSentinel, program.namespace)
}
fun visit(module: Module, parent: Node) {

View File

@ -0,0 +1,11 @@
package prog8.compilerinterface
import prog8.ast.base.DataType
// note: this is a separate interface in the compilerAst module because
// otherwise a cyclic dependency with the compilerInterfaces module would be needed.
interface IMemSizer {
fun memorySize(dt: DataType): Int
}

View File

@ -1,6 +1,6 @@
package prog8.compiler
package prog8.compilerinterface
interface IStringEncoding {
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
}
}

View File

@ -8,14 +8,7 @@ import prog8.ast.statements.Block
import prog8.ast.statements.Directive
open class ParsingFailedError(override var message: String) : Exception(message)
class ParseError(override var message: String, val position: Position, cause: RuntimeException)
: ParsingFailedError("${position.toClickableStr()}$message") {
init {
initCause(cause)
}
}
class ParseError(override var message: String, val position: Position, cause: RuntimeException): Exception(message, cause)
object Prog8Parser {
@ -107,7 +100,7 @@ object Prog8Parser {
val offending = this.offendingToken
val line = offending.line
val beginCol = offending.charPositionInLine
val endCol = beginCol + offending.stopIndex - offending.startIndex // TODO: point to col *after* token? / why, what's wrong with endCol being inclusive
val endCol = beginCol + offending.stopIndex - offending.startIndex
return Position(file, line, beginCol, endCol)
}

View File

@ -8,9 +8,7 @@ import java.nio.channels.Channels
import java.nio.charset.CodingErrorAction
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.isReadable
import kotlin.io.path.*
/**
* Encapsulates - and ties together - actual source code (=text)
@ -45,9 +43,8 @@ sealed class SourceCode {
/**
* The source code as plain string.
* *Note: this is meant for testing and debugging, do NOT use in application code!*
*/
fun asString() = this.getCharStream().toString()
abstract fun readText(): String
/**
* Deliberately does NOT return the actual text.
@ -62,7 +59,7 @@ sealed class SourceCode {
*/
const val libraryFilePrefix = "library:"
const val stringSourcePrefix = "<String@"
val curdir: Path = Path.of(".").toAbsolutePath()
val curdir: Path = Path(".").toAbsolutePath()
fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath())
fun isRegularFilesystemPath(pathString: String) =
!(pathString.startsWith(libraryFilePrefix) || pathString.startsWith(stringSourcePrefix))
@ -77,6 +74,7 @@ sealed class SourceCode {
override val isFromFilesystem = false
override val origin = "$stringSourcePrefix${System.identityHashCode(text).toString(16)}>"
override fun getCharStream(): CharStream = CharStreams.fromString(text, origin)
override fun readText() = text
}
/**
@ -107,6 +105,7 @@ sealed class SourceCode {
override val isFromFilesystem = true
override val origin = relative(normalized).toString()
override fun getCharStream(): CharStream = CharStreams.fromPath(normalized)
override fun readText() = normalized.readText()
}
/**
@ -129,12 +128,17 @@ sealed class SourceCode {
override val isFromResources = true
override val isFromFilesystem = false
override val origin = "$libraryFilePrefix$normalized"
override fun getCharStream(): CharStream {
public override fun getCharStream(): CharStream {
val inpStr = object {}.javaClass.getResourceAsStream(normalized)!!
// CharStreams.fromStream() doesn't allow us to set the stream name properly, so we use a lower level api
val channel = Channels.newChannel(inpStr)
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1);
}
override fun readText(): String {
val stream = object {}.javaClass.getResourceAsStream(normalized)
return stream!!.bufferedReader().use { r -> r.readText() }
}
}
/**
@ -145,15 +149,6 @@ sealed class SourceCode {
override val isFromResources: Boolean = false
override val isFromFilesystem: Boolean = false
override val origin: String = name
override fun readText() = throw IOException("generated code nodes don't have a text representation")
}
// TODO: possibly more, like fromURL(..)
/* // For `jar:..` URLs
// see https://stackoverflow.com/questions/22605666/java-access-files-in-jar-causes-java-nio-file-filesystemnotfoundexception
var url = URL("jar:file:/E:/x16/prog8(meisl)/compiler/build/libs/prog8compiler-7.0-BETA3-all.jar!/prog8lib/c64/textio.p8")
val uri = url.toURI()
val parts = uri.toString().split("!")
val fs = FileSystems.newFileSystem(URI.create(parts[0]), mutableMapOf(Pair("", "")) )
val path = fs.getPath(parts[1])
*/
}

View File

@ -1,28 +1,29 @@
package prog8tests
package prog8tests.ast
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.AstToSourceCode
import prog8.ast.AstToSourceTextConverter
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.internedStringsModuleName
import prog8.parser.ParseError
import prog8.parser.Prog8Parser.parseModule
import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
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 TestAstToSourceCode {
class TestAstToSourceText {
private fun generateP8(module: Module) : String {
val program = Program("test", DummyFunctions, DummyMemsizer)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
var generatedText = ""
val it = AstToSourceCode({ str -> generatedText += str }, program)
val it = AstToSourceTextConverter({ str -> generatedText += str }, program)
it.visit(program)
return generatedText

View File

@ -1,4 +1,4 @@
package prog8tests
package prog8tests.ast
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@ -6,6 +6,8 @@ import org.junit.jupiter.api.TestInstance
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
@ -15,9 +17,9 @@ import prog8.ast.statements.*
import prog8.parser.ParseError
import prog8.parser.Prog8Parser.parseModule
import prog8.parser.SourceCode
import prog8tests.helpers.assumeNotExists
import prog8tests.helpers.assumeReadableFile
import prog8tests.helpers.fixturesDir
import prog8tests.ast.helpers.*
import prog8tests.ast.helpers.DummyFunctions
import prog8tests.ast.helpers.DummyMemsizer
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
import kotlin.io.path.name
@ -285,24 +287,16 @@ class TestProg8Parser {
fun `in ParseError from bad string source code`() {
val srcText = "bad * { }\n"
assertFailsWith<ParseError> { parseModule(SourceCode.Text(srcText)) }
try {
parseModule(SourceCode.Text(srcText))
} catch (e: ParseError) {
assertPosition(e.position, Regex("^<String@[0-9a-f\\-]+>$"), 1, 4, 4)
}
val e = assertFailsWith<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`() {
val path = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
assertFailsWith<ParseError> { parseModule(SourceCode.File(path)) }
try {
parseModule(SourceCode.File(path))
} catch (e: ParseError) {
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6) // TODO: endCol wrong
}
val e = assertFailsWith<ParseError> { parseModule(SourceCode.File(path)) }
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6)
}
@Test
@ -310,16 +304,16 @@ class TestProg8Parser {
val srcText = """
main {
}
""".trimIndent()
"""
val module = parseModule(SourceCode.Text(srcText))
assertPositionOf(module, Regex("^<String@[0-9a-f\\-]+>$"), 1, 0) // TODO: endCol wrong
assertPositionOf(module, Regex("^<String@[0-9a-f\\-]+>$"), 1, 0)
}
@Test
fun `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) // TODO: endCol wrong
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
}
@Test
@ -328,27 +322,24 @@ class TestProg8Parser {
val module = parseModule(SourceCode.File(path))
val mpf = module.position.file
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0) // TODO: endCol wrong
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
val mainBlock = module.statements.filterIsInstance<Block>()[0]
assertPositionOf(mainBlock, mpf, 2, 0) // TODO: endCol wrong!
assertPositionOf(mainBlock, mpf, 2, 0, 3)
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
assertPositionOf(startSub, mpf, 3, 4) // TODO: endCol wrong!
assertPositionOf(startSub, mpf, 3, 4, 6)
}
/**
* TODO: this test is testing way too much at once
*/
@Test
fun `of non-root Nodes parsed from a string`() {
val srcText = """
%zeropage basicsafe ; DirectiveArg directly inherits from Node - neither an Expression nor a Statement..?
%zeropage basicsafe
main {
sub start() {
ubyte foo = 42
ubyte bar
when (foo) {
23 -> bar = 'x' ; WhenChoice, also directly inheriting Node
23 -> bar = 'x'
42 -> bar = 'y'
else -> bar = 'z'
}
@ -359,22 +350,22 @@ class TestProg8Parser {
val mpf = module.position.file
val targetDirective = module.statements.filterIsInstance<Directive>()[0]
assertPositionOf(targetDirective, mpf, 1, 0) // TODO: endCol wrong!
assertPositionOf(targetDirective, mpf, 1, 0, 8)
val mainBlock = module.statements.filterIsInstance<Block>()[0]
assertPositionOf(mainBlock, mpf, 2, 0) // TODO: endCol wrong!
assertPositionOf(mainBlock, mpf, 2, 0, 3)
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
assertPositionOf(startSub, mpf, 3, 4) // TODO: endCol wrong!
assertPositionOf(startSub, mpf, 3, 4, 6)
val declFoo = startSub.statements.filterIsInstance<VarDecl>()[0]
assertPositionOf(declFoo, mpf, 4, 8) // TODO: endCol wrong!
assertPositionOf(declFoo, mpf, 4, 8, 12)
val rhsFoo = declFoo.value!!
assertPositionOf(rhsFoo, mpf, 4, 20) // TODO: endCol wrong!
assertPositionOf(rhsFoo, mpf, 4, 20, 21)
val declBar = startSub.statements.filterIsInstance<VarDecl>()[1]
assertPositionOf(declBar, mpf, 5, 8) // TODO: endCol wrong!
assertPositionOf(declBar, mpf, 5, 8, 12)
val whenStmt = startSub.statements.filterIsInstance<WhenStatement>()[0]
assertPositionOf(whenStmt, mpf, 6, 8) // TODO: endCol wrong!
assertPositionOf(whenStmt.choices[0], mpf, 7, 12) // TODO: endCol wrong!
assertPositionOf(whenStmt.choices[1], mpf, 8, 12) // TODO: endCol wrong!
assertPositionOf(whenStmt.choices[2], mpf, 9, 12) // TODO: endCol wrong!
assertPositionOf(whenStmt, mpf, 6, 8, 11)
assertPositionOf(whenStmt.choices[0], mpf, 7, 12, 13)
assertPositionOf(whenStmt.choices[1], mpf, 8, 12, 13)
assertPositionOf(whenStmt.choices[2], mpf, 9, 12, 15)
}
}
@ -581,4 +572,95 @@ class TestProg8Parser {
}
}
@Test
fun 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())
}
@Test
fun testLiteralValueComparisons() {
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
assertTrue(ten != nine)
assertTrue(ten > nine)
assertTrue(ten >= nine)
assertTrue(ten >= ten)
assertFalse(ten > ten)
assertFalse(ten < nine)
assertFalse(ten <= nine)
assertTrue(ten <= ten)
assertFalse(ten < ten)
val abc = StringLiteralValue("abc", false, Position.DUMMY)
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
}
@Test
fun testAnonScopeStillContainsVarsDirectlyAfterParse() {
val src = SourceCode.Text("""
main {
sub start() {
repeat {
ubyte xx = 99
xx++
}
}
}
""")
val module = parseModule(src)
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)")
val initvalue = (repeatbody.statements[0] as VarDecl).value as? NumericLiteralValue
assertEquals(99, initvalue?.number?.toInt())
assertTrue(repeatbody.statements[1] is PostIncrDecr)
// the ast processing steps used in the compiler, will eventually move the var up to the containing scope (subroutine).
}
@Test
fun testLabelsWithAnonScopesParsesFine() {
val src = SourceCode.Text("""
main {
sub start() {
goto mylabeloutside
if true {
if true {
goto labeloutside
goto iflabel
}
iflabel:
}
repeat {
goto labelinside
labelinside:
}
labeloutside:
}
}
""")
val module = parseModule(src)
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")
}
}

View File

@ -1,4 +1,4 @@
package prog8tests
package prog8tests.ast
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.StringStartsWith
@ -6,7 +6,10 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.parser.SourceCode
import prog8.parser.SourceCode.Companion.libraryFilePrefix
import prog8tests.helpers.*
import prog8tests.ast.helpers.assumeNotExists
import prog8tests.ast.helpers.assumeReadableFile
import prog8tests.ast.helpers.fixturesDir
import prog8tests.ast.helpers.resourcesDir
import kotlin.io.path.Path
import kotlin.test.*
@ -55,7 +58,7 @@ class TestSourceCode {
val src = SourceCode.File(path)
val expectedOrigin = SourceCode.relative(path).toString()
assertEquals(expectedOrigin, src.origin)
assertEquals(path.toFile().readText(), src.asString())
assertEquals(path.toFile().readText(), src.readText())
assertFalse(src.isFromResources)
assertTrue(src.isFromFilesystem)
}
@ -68,7 +71,7 @@ class TestSourceCode {
val src = SourceCode.File(path)
val expectedOrigin = SourceCode.relative(path).toString()
assertEquals(expectedOrigin, src.origin)
assertEquals(srcFile.readText(), src.asString())
assertEquals(srcFile.readText(), src.readText())
}
@Test
@ -78,7 +81,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix/$pathString", src.origin)
assertEquals(srcFile.readText(), src.asString())
assertEquals(srcFile.readText(), src.readText())
assertTrue(src.isFromResources)
assertFalse(src.isFromFilesystem)
}
@ -90,7 +93,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix$pathString", src.origin)
assertEquals(srcFile.readText(), src.asString())
assertEquals(srcFile.readText(), src.readText())
}
@Test
@ -100,7 +103,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix/$pathString", src.origin)
assertEquals(srcFile.readText(), src.asString())
assertEquals(srcFile.readText(), src.readText())
assertTrue(src.isFromResources, ".isFromResources")
}
@ -111,7 +114,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix$pathString", src.origin)
assertEquals(srcFile.readText(), src.asString())
assertEquals(srcFile.readText(), src.readText())
}
@Test
@ -121,7 +124,7 @@ class TestSourceCode {
val src = SourceCode.Resource(pathString)
assertEquals("$libraryFilePrefix/prog8lib/math.p8", src.origin)
assertEquals(srcFile.readText(), src.asString())
assertEquals(srcFile.readText(), src.readText())
assertTrue(src.isFromResources, ".isFromResources")
}

View File

@ -0,0 +1,69 @@
package prog8tests.ast
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
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 {
@Test
fun stringParameterAcceptedInParser() {
// note: the *parser* should accept this as it is valid *syntax*,
// however, the compiler itself may or may not complain about it later.
val text = """
main {
asmsub asmfunc(str thing @AY) {
}
sub func(str thing) {
}
}
"""
val src = SourceCode.Text(text)
val module = parseModule(src)
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())
}
@Test
fun arrayParameterAcceptedInParser() {
// note: the *parser* should accept this as it is valid *syntax*,
// however, the compiler itself may or may not complain about it later.
val text = """
main {
asmsub asmfunc(ubyte[] thing @AY) {
}
sub func(ubyte[22] thing) {
}
}
"""
val src = SourceCode.Text(text)
val module = parseModule(src)
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())
}
}

View File

@ -1,5 +1,4 @@
package prog8tests.ast
package prog8tests.ast.ast
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.containsString
@ -12,8 +11,9 @@ import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.internedStringsModuleName
import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
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
@ -26,7 +26,7 @@ class ProgramTests {
inner class Constructor {
@Test
fun withNameBuiltinsAndMemsizer() {
val program = Program("foo", DummyFunctions, DummyMemsizer)
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)
@ -39,7 +39,7 @@ class ProgramTests {
inner class AddModule {
@Test
fun withEmptyModule() {
val program = Program("foo", DummyFunctions, DummyMemsizer)
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
val retVal = program.addModule(m1)
@ -63,7 +63,7 @@ class ProgramTests {
inner class MoveModuleToFront {
@Test
fun withInternedStringsModule() {
val program = Program("foo", DummyFunctions, DummyMemsizer)
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val m = program.modules[0]
assertThat(m.name, equalTo(internedStringsModuleName))
@ -73,14 +73,14 @@ class ProgramTests {
}
@Test
fun withForeignModule() {
val program = Program("foo", DummyFunctions, DummyMemsizer)
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)
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)
@ -92,7 +92,7 @@ class ProgramTests {
}
@Test
fun withSecondOfPreviouslyAddedModules() {
val program = Program("foo", DummyFunctions, DummyMemsizer)
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)
@ -108,7 +108,7 @@ class ProgramTests {
inner class Properties {
@Test
fun modules() {
val program = Program("foo", DummyFunctions, DummyMemsizer)
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val ms1 = program.modules
val ms2 = program.modules

View File

@ -0,0 +1,44 @@
package prog8tests.ast.helpers
import prog8.ast.IBuiltinFunctions
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
internal val DummyFunctions = object : IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(
name: String,
args: List<Expression>,
position: Position,
): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return ""
}
}
internal val AsciiStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> = str.map { it.code.toShort() }
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
return bytes.joinToString()
}
}

View File

@ -1,8 +0,0 @@
package prog8tests.helpers
import prog8.ast.IMemSizer
import prog8.ast.base.DataType
val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}

Some files were not shown because too many files have changed in this diff Show More