mirror of
https://github.com/irmen/prog8.git
synced 2026-10-23 12:02:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35998142fe | ||
|
|
75ea453bf4 | ||
|
|
33061aaa0d | ||
|
|
e342311bef | ||
|
|
3d743a1ba1 | ||
|
|
abca618008 | ||
|
|
0d2c3901a3 | ||
|
|
d901a1531f | ||
|
|
d8d56b195f | ||
|
|
a52699717c | ||
|
|
98315de723 | ||
|
|
68d2f7d4c0 | ||
|
|
c812b5ee09 | ||
|
|
dcf487bdc1 | ||
|
|
547b1d3720 | ||
|
|
84f75f4156 | ||
|
|
ff69da3fa2 | ||
|
|
edffe92a24 | ||
|
|
b6fe40ada4 | ||
|
|
837804b231 | ||
|
|
81deed143b | ||
|
|
900cdd3fa1 | ||
|
|
0018dc6ce7 | ||
|
|
c92f914081 | ||
|
|
0498444ef2 | ||
|
|
ce3c34e458 | ||
|
|
20401b99d8 | ||
|
|
397f98513b | ||
|
|
e545ea9504 | ||
|
|
b867d8f731 | ||
|
|
9a68864b67 | ||
|
|
72d7178762 | ||
|
|
fbcd9a0c1d | ||
|
|
c3144a20db | ||
|
|
5b56e0462d | ||
|
|
b7fffbb6df |
No files matched your search
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
+1
@@ -7,6 +7,7 @@
|
||||
<language isEnabled="false" name="Groovy" />
|
||||
</Languages>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
||||
<option name="processCode" value="false" />
|
||||
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="hamcrest" type="repository">
|
||||
<properties maven-id="org.hamcrest:hamcrest:2.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-17
@@ -1,17 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="junit.jupiter" type="repository">
|
||||
<properties maven-id="org.junit.jupiter:junit-jupiter:5.7.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.7.2/junit-jupiter-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.7.2/junit-jupiter-params-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.7.2/junit-jupiter-engine-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+1
-1
@@ -19,7 +19,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
+13
-19
@@ -3,7 +3,6 @@ plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id "io.kotest" version "0.3.8"
|
||||
}
|
||||
|
||||
java {
|
||||
@@ -12,6 +11,18 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':compilerInterfaces')
|
||||
implementation project(':compilerAst')
|
||||
@@ -19,7 +30,6 @@ dependencies {
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -31,22 +41,6 @@ sourceSets {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
// note: there are no unit tests in this module!
|
||||
@@ -4,16 +4,13 @@
|
||||
<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="inheritedJdk" />
|
||||
<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" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -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.
|
||||
@@ -11,9 +11,10 @@ import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object C64Target: ICompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition
|
||||
override val machine = C64MachineDefinition()
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
|
||||
@@ -17,9 +17,9 @@ import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
object Cx16Target: ICompilationTarget {
|
||||
override val name = "cx16"
|
||||
override val machine = CX16MachineDefinition
|
||||
override val machine = CX16MachineDefinition()
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compiler.target.cbm.Mflpt5
|
||||
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
|
||||
|
||||
object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
class C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801u
|
||||
override val RAW_LOAD_ADDRESS = 0xc000u
|
||||
@@ -57,6 +57,7 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> = emptyMap()
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
@@ -74,133 +75,4 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
||||
|
||||
|
||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x03u // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fb+$fc
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
free.addAll(0x04u..0xf9u)
|
||||
free.add(0xffu)
|
||||
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
// 0x90-0xfa is 'kernal work storage area'
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zeropage locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||
// 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,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
free.clear()
|
||||
}
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
|
||||
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte):
|
||||
IMachineFloat {
|
||||
|
||||
companion object {
|
||||
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||
|
||||
val flt = num.toDouble()
|
||||
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||
if (flt == 0.0)
|
||||
return zero
|
||||
|
||||
val sign = if (flt < 0.0) 0x80L else 0x00L
|
||||
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
||||
var mantissa = flt.absoluteValue
|
||||
|
||||
// if mantissa is too large, shift right and adjust exponent
|
||||
while (mantissa >= 0x100000000) {
|
||||
mantissa /= 2.0
|
||||
exponent++
|
||||
}
|
||||
// if mantissa is too small, shift left and adjust exponent
|
||||
while (mantissa < 0x80000000) {
|
||||
mantissa *= 2.0
|
||||
exponent--
|
||||
}
|
||||
|
||||
return when {
|
||||
exponent < 0 -> zero // underflow, use zero instead
|
||||
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
|
||||
exponent == 0 -> zero
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
Mflpt5(
|
||||
exponent.toUByte(),
|
||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
|
||||
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
|
||||
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
|
||||
(mantLong.and(0x000000ffL)).toUByte())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toDouble(): Double {
|
||||
if (this == zero) return 0.0
|
||||
val exp = b0.toInt() - 128
|
||||
val sign = (b1.toInt() and 0x80) > 0
|
||||
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||
return if (sign) -result else result
|
||||
}
|
||||
|
||||
override fun makeFloatFillAsm(): String {
|
||||
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
||||
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
||||
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
||||
val b3 = "$" + b3.toString(16).padStart(2, '0')
|
||||
val b4 = "$" + b4.toString(16).padStart(2, '0')
|
||||
return "$b0, $b1, $b2, $b3, $b4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import prog8.compilerinterface.Zeropage
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
|
||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x03u // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fb+$fc
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE,
|
||||
ZeropageType.DONTUSE
|
||||
))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
free.addAll(0x04u..0xf9u)
|
||||
free.add(0xffu)
|
||||
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
// 0x90-0xfa is 'kernal work storage area'
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zeropage locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||
// 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,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
free.clear()
|
||||
}
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package prog8.compiler.target.cbm
|
||||
|
||||
import prog8.compilerinterface.IMachineFloat
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte): IMachineFloat {
|
||||
|
||||
companion object {
|
||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
const val FLOAT_MEM_SIZE = 5
|
||||
|
||||
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||
|
||||
val flt = num.toDouble()
|
||||
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||
if (flt == 0.0)
|
||||
return zero
|
||||
|
||||
val sign = if (flt < 0.0) 0x80L else 0x00L
|
||||
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
||||
var mantissa = flt.absoluteValue
|
||||
|
||||
// if mantissa is too large, shift right and adjust exponent
|
||||
while (mantissa >= 0x100000000) {
|
||||
mantissa /= 2.0
|
||||
exponent++
|
||||
}
|
||||
// if mantissa is too small, shift left and adjust exponent
|
||||
while (mantissa < 0x80000000) {
|
||||
mantissa *= 2.0
|
||||
exponent--
|
||||
}
|
||||
|
||||
return when {
|
||||
exponent < 0 -> zero // underflow, use zero instead
|
||||
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
|
||||
exponent == 0 -> zero
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
Mflpt5(
|
||||
exponent.toUByte(),
|
||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
|
||||
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
|
||||
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
|
||||
(mantLong.and(0x000000ffL)).toUByte()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toDouble(): Double {
|
||||
if (this == zero) return 0.0
|
||||
val exp = b0.toInt() - 128
|
||||
val sign = (b1.toInt() and 0x80) > 0
|
||||
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||
return if (sign) -result else result
|
||||
}
|
||||
|
||||
override fun makeFloatFillAsm(): String {
|
||||
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
||||
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
||||
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
||||
val b3 = "$" + b3.toString(16).padStart(2, '0')
|
||||
val b4 = "$" + b4.toString(16).padStart(2, '0')
|
||||
return "$b0, $b1, $b2, $b3, $b4"
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ class AsmGen(private val program: Program,
|
||||
|
||||
private val assemblyLines = mutableListOf<String>()
|
||||
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
private val allocatedZeropageVariables = mutableMapOf<String, Pair<UInt, DataType>>()
|
||||
private val allocatedZeropageVariables = compTarget.machine.getPreallocatedZeropageVars().toMutableMap()
|
||||
private val breakpointLabels = mutableListOf<String>()
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
|
||||
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
|
||||
@@ -79,6 +79,10 @@ class AsmGen(private val program: Program,
|
||||
footer()
|
||||
|
||||
if(options.optimize) {
|
||||
val separateLines = assemblyLines.flatMap { it.split('\n') }
|
||||
assemblyLines.clear()
|
||||
assemblyLines.addAll(separateLines)
|
||||
require(assemblyLines.all { !it.contains('\n') })
|
||||
var optimizationsDone = 1
|
||||
while (optimizationsDone > 0) {
|
||||
optimizationsDone = optimizeAssembly(assemblyLines, options.compTarget.machine, program)
|
||||
@@ -545,16 +549,16 @@ class AsmGen(private val program: Program,
|
||||
fun asmVariableName(identifier: IdentifierReference) =
|
||||
fixNameSymbols(identifier.nameInSource.joinToString("."))
|
||||
|
||||
internal fun asmSymbolName(regs: RegisterOrPair): String =
|
||||
fun asmSymbolName(regs: RegisterOrPair): String =
|
||||
if (regs in Cx16VirtualRegisters)
|
||||
"cx16." + regs.toString().lowercase()
|
||||
else
|
||||
throw AssemblyError("no symbol name for register $regs")
|
||||
|
||||
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
|
||||
internal fun asmVariableName(name: String) = fixNameSymbols(name)
|
||||
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
internal fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
fun asmSymbolName(name: String) = fixNameSymbols(name)
|
||||
fun asmVariableName(name: String) = fixNameSymbols(name)
|
||||
fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
|
||||
|
||||
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): String {
|
||||
@@ -604,6 +608,52 @@ class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun storeAIntoPointerVar(pointervar: IdentifierReference) {
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program)!!
|
||||
val scopedName = vardecl.scopedName.joinToString(".")
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
if (isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" sta ($sourceName)")
|
||||
} else {
|
||||
out("""
|
||||
ldy $sourceName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy $sourceName+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
sta (P8ZP_SCRATCH_W2)""")
|
||||
}
|
||||
} else {
|
||||
if (isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" ldy #0 | sta ($sourceName),y")
|
||||
} else {
|
||||
out("""
|
||||
ldy $sourceName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy $sourceName+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun storeAIntoZpPointerVar(zpPointerVar: String) {
|
||||
if (isTargetCpu(CpuType.CPU65c02))
|
||||
out(" sta ($zpPointerVar)")
|
||||
else
|
||||
out(" ldy #0 | sta ($zpPointerVar),y")
|
||||
}
|
||||
|
||||
internal fun loadAFromZpPointerVar(zpPointerVar: String) {
|
||||
if (isTargetCpu(CpuType.CPU65c02))
|
||||
out(" lda ($zpPointerVar)")
|
||||
else
|
||||
out(" ldy #0 | lda ($zpPointerVar),y")
|
||||
}
|
||||
|
||||
private fun fixNameSymbols(name: String): String {
|
||||
val name2 = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
||||
return name2.replace("prog8_lib.P8ZP_SCRATCH_", "P8ZP_SCRATCH_") // take care of the 'hooks' to the temp vars
|
||||
@@ -777,7 +827,7 @@ class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | sec | rol a")
|
||||
out(" lda $indexName | sec | rol a")
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
}
|
||||
@@ -808,7 +858,7 @@ class AsmGen(private val program: Program,
|
||||
when (elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
out(" lda $indexName | asl a")
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
}
|
||||
@@ -1247,7 +1297,6 @@ $repeatLabel lda $counterVar
|
||||
if(choice.values==null) {
|
||||
// the else choice
|
||||
translate(choice.statements)
|
||||
jmp(endLabel)
|
||||
} else {
|
||||
choiceBlocks.add(choiceLabel to choice.statements)
|
||||
for (cv in choice.values!!) {
|
||||
@@ -1267,10 +1316,11 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
jmp(endLabel)
|
||||
for(choiceBlock in choiceBlocks) {
|
||||
out(choiceBlock.first)
|
||||
translate(choiceBlock.second)
|
||||
jmp(endLabel)
|
||||
for(choiceBlock in choiceBlocks.withIndex()) {
|
||||
out(choiceBlock.value.first)
|
||||
translate(choiceBlock.value.second)
|
||||
if(choiceBlock.index<choiceBlocks.size-1)
|
||||
jmp(endLabel)
|
||||
}
|
||||
out(endLabel)
|
||||
}
|
||||
@@ -1384,14 +1434,15 @@ $label nop""")
|
||||
|
||||
private fun translate(jump: Jump) {
|
||||
if(jump.isGosub) {
|
||||
jump as GoSub
|
||||
val tgt = jump.identifier!!.targetSubroutine(program)
|
||||
if(tgt!=null && tgt.isAsmSubroutine) {
|
||||
// no need to rescue X , this has been taken care of already
|
||||
out(" jsr ${getJumpTarget(jump)}")
|
||||
} else {
|
||||
saveXbeforeCall(jump as GoSub)
|
||||
saveXbeforeCall(jump)
|
||||
out(" jsr ${getJumpTarget(jump)}")
|
||||
restoreXafterCall(jump as GoSub)
|
||||
restoreXafterCall(jump)
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1483,7 +1534,8 @@ $label nop""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun isZpVar(scopedName: String): Boolean = scopedName in allocatedZeropageVariables
|
||||
internal fun isZpVar(scopedName: String): Boolean =
|
||||
scopedName in allocatedZeropageVariables
|
||||
|
||||
internal fun isZpVar(variable: IdentifierReference): Boolean {
|
||||
val vardecl = variable.targetVarDecl(program)!!
|
||||
@@ -3197,5 +3249,4 @@ $label nop""")
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -347,8 +347,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
private fun funcCmp(fcall: IFunctionCall) {
|
||||
val arg1 = fcall.args[0]
|
||||
val arg2 = fcall.args[1]
|
||||
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val dt1 = arg1.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val dt2 = arg2.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes) {
|
||||
when (arg2) {
|
||||
@@ -992,9 +992,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
|
||||
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
|
||||
val elementIDt = first.inferType(program)
|
||||
if(!elementIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
||||
val elementDt = elementIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
|
||||
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
|
||||
val firstVar = first.indexer.indexExpr as? IdentifierReference
|
||||
@@ -1646,7 +1644,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
|
||||
val callConv = signature.callConvention(args.map { it.inferType(program).getOr(DataType.UNDEFINED) })
|
||||
val callConv = signature.callConvention(args.map {
|
||||
it.inferType(program).getOrElse { throw AssemblyError("unknown dt")}
|
||||
})
|
||||
|
||||
fun getSourceForFloat(value: Expression): AsmAssignSource {
|
||||
return when (value) {
|
||||
|
||||
@@ -254,8 +254,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
throw AssemblyError("can't infer type of both expression operands")
|
||||
|
||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||
val leftDt = leftIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
val rightDt = rightIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
// see if we can apply some optimized routines
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
@@ -420,7 +420,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_w_$left")
|
||||
}
|
||||
@@ -578,9 +578,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
private fun translateExpression(expr: PrefixExpression) {
|
||||
translateExpressionInternal(expr.expression)
|
||||
val itype = expr.inferType(program)
|
||||
if(!itype.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val type = itype.getOr(DataType.UNDEFINED)
|
||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
||||
when(expr.operator) {
|
||||
"+" -> {}
|
||||
"-" -> {
|
||||
|
||||
@@ -22,13 +22,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
is RangeExpr -> {
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||
if(range==null) {
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpr)
|
||||
} else {
|
||||
translateForOverConstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), range)
|
||||
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
translateForOverIterableVar(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as IdentifierReference)
|
||||
translateForOverIterableVar(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as IdentifierReference)
|
||||
}
|
||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||
}
|
||||
@@ -589,5 +589,9 @@ $loopLabel""")
|
||||
}
|
||||
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
|
||||
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).getOr(DataType.UNDEFINED), stmt.definingSubroutine)
|
||||
asmgen.assignExpressionToVariable(
|
||||
range.from,
|
||||
asmgen.asmVariableName(stmt.loopVar),
|
||||
stmt.loopVarDt(program).getOrElse { throw AssemblyError("unknown dt") },
|
||||
stmt.definingSubroutine)
|
||||
}
|
||||
@@ -254,9 +254,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
@@ -267,9 +265,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass argument via a register parameter
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
|
||||
+5
-11
@@ -59,9 +59,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
|
||||
with(assign.target) {
|
||||
val idt = inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val dt = idt.getOr(DataType.UNDEFINED)
|
||||
val dt = idt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
when {
|
||||
identifier != null -> {
|
||||
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
|
||||
@@ -144,7 +142,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||
|
||||
return when(value) {
|
||||
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv)
|
||||
is NumericLiteralValue -> throw AssemblyError("should have been constant value")
|
||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
@@ -166,7 +164,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
@@ -179,9 +177,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val returnType = value.inferType(program)
|
||||
if(!returnType.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOr(DataType.UNDEFINED), expression = value)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird call")
|
||||
@@ -190,9 +186,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
}
|
||||
else -> {
|
||||
val dt = value.inferType(program)
|
||||
if(!dt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.getOr(DataType.UNDEFINED), expression = value)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-54
@@ -125,10 +125,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
SourceStorageKind.MEMORY -> {
|
||||
fun assignViaExprEval(expression: Expression) {
|
||||
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD, assign.target.scope)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
}
|
||||
|
||||
@@ -317,9 +314,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
private fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: Expression, origTypeCastExpression: TypecastExpression) {
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(valueDt==targetDt)
|
||||
throw AssemblyError("type cast to identical dt should have been removed")
|
||||
|
||||
@@ -341,10 +336,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
fun assignViaExprEval(addressExpression: Expression) {
|
||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
assignRegisterByte(target, CpuRegister.A)
|
||||
}
|
||||
|
||||
@@ -1660,7 +1652,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" stz ${target.asmVarname} | stz ${target.asmVarname}+1")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
|
||||
throw AssemblyError("memory is bytes not words")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y)
|
||||
@@ -1759,7 +1751,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" stz ${target.asmVarname} ")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
asmgen.out(" lda #${byte.toHex()}")
|
||||
asmgen.out(" lda #0")
|
||||
storeRegisterAInMemoryAddress(target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
@@ -2140,52 +2132,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
when(addressExpr) {
|
||||
is NumericLiteralValue, is IdentifierReference -> {
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
else -> {
|
||||
// same as above but we need to save the A register
|
||||
asmgen.out(" pha")
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
asmgen.out(" pla")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program)!!
|
||||
val scopedName = vardecl.scopedName.joinToString(".")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if (asmgen.isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.out(" sta ($sourceName)")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy $sourceName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy $sourceName+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
sta (P8ZP_SCRATCH_W2)""")
|
||||
}
|
||||
} else {
|
||||
if (asmgen.isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.out(" ldy #0 | sta ($sourceName),y")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy $sourceName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy $sourceName+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2195,7 +2149,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" sta ${addressLv.number.toHex()}")
|
||||
}
|
||||
addressExpr is IdentifierReference -> {
|
||||
storeAIntoPointerVar(addressExpr)
|
||||
asmgen.storeAIntoPointerVar(addressExpr)
|
||||
}
|
||||
addressExpr is BinaryExpression -> {
|
||||
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, true))
|
||||
|
||||
+36
-43
@@ -23,9 +23,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
// A = -A , A = +A, A = ~A, A = not A
|
||||
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
|
||||
val itype = value.inferType(program)
|
||||
if(!itype.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val type = itype.getOr(DataType.UNDEFINED)
|
||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
||||
when (value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> inplaceNegate(target, type)
|
||||
@@ -153,7 +151,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
// the asm-gen code can deal with situations where you want to assign a byte into a word.
|
||||
// it will create the most optimized code to do this (so it type-extends for us).
|
||||
// But we can't deal with writing a word into a byte - explicit typeconversion is required
|
||||
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOr(DataType.UNDEFINED)) > program.memsizer.memorySize(target.datatype)) {
|
||||
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOrElse { throw AssemblyError("unknown dt") }) > program.memsizer.memorySize(target.datatype)) {
|
||||
val typecast = TypecastExpression(origValue, target.datatype, true, origValue.position)
|
||||
typecast.linkParents(origValue.parent)
|
||||
typecast
|
||||
@@ -237,7 +235,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// TODO OTHER EVALUATION HERE, don't use the estack to transfer the address to read/write from
|
||||
// TODO use some other evaluation here; don't use the estack to transfer the address to read/write from
|
||||
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||
when {
|
||||
@@ -353,9 +351,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private fun tryRemoveRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
|
||||
if (target.datatype == value.type) {
|
||||
val childIDt = value.expression.inferType(program)
|
||||
if(!childIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val childDt = childIDt.getOr(DataType.UNDEFINED)
|
||||
val childDt = childIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
|
||||
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
|
||||
// (works for integer types, not for float.)
|
||||
@@ -368,13 +364,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
|
||||
private fun inplaceModification_byte_value_to_pointer(pointervar: IdentifierReference, operator: String, value: Expression) {
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
|
||||
"-" -> asmgen.out(" sec | sbc P8ZP_SCRATCH_B1")
|
||||
"*" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.multiply_bytes | ldy #0")
|
||||
"/" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
"%" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm | ldy #0")
|
||||
"*" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.multiply_bytes")
|
||||
"/" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm | tya")
|
||||
"%" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm")
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
@@ -398,8 +395,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_variable_to_pointer(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
|
||||
@@ -410,9 +406,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> asmgen.out(" clc | adc $otherName")
|
||||
"-" -> asmgen.out(" sec | sbc $otherName")
|
||||
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes | ldy #0")
|
||||
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | ldy #0")
|
||||
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes")
|
||||
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya")
|
||||
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm")
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
@@ -436,7 +432,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"^", "xor" -> asmgen.out(" eor $otherName")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_litval_to_pointer(pointervar: IdentifierReference, operator: String, value: Int) {
|
||||
@@ -445,63 +441,63 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"+" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" clc | adc #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"-" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sec | sbc #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"*" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" jsr math.mul_byte_${value}")
|
||||
else
|
||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes | ldy #0")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"/" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"%" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | ldy #0")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"<<" -> {
|
||||
if (value > 0) {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
repeat(value) { asmgen.out(" asl a") }
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
repeat(value) { asmgen.out(" asl a") }
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
if (value > 0) {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
repeat(value) { asmgen.out(" lsr a") }
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
repeat(value) { asmgen.out(" lsr a") }
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
}
|
||||
"&", "and" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" and #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"|", "or" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" ora #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"^", "xor" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" eor #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
@@ -1292,9 +1288,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
// because the value is evaluated onto the eval stack (=slow).
|
||||
|
||||
val valueiDt = value.inferType(program)
|
||||
if(!valueiDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueiDt.getOr(DataType.UNDEFINED)
|
||||
val valueDt = valueiDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
|
||||
fun multiplyVarByWordInAY() {
|
||||
asmgen.out("""
|
||||
@@ -1811,17 +1805,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(mem.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
+ eor #1""")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1942,8 +1935,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
eor #255
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
eor #255""")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2087,7 +2080,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for stack float negate")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
|
||||
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
package prog8.compiler.target.cx16
|
||||
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compiler.target.cbm.Mflpt5
|
||||
import prog8.compiler.target.cbm.viceMonListPostfix
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
object CX16MachineDefinition: IMachineDefinition {
|
||||
class CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU65c02
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801u
|
||||
override val RAW_LOAD_ADDRESS = 0x8000u
|
||||
@@ -25,7 +25,7 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
@@ -68,6 +68,18 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
|
||||
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> {
|
||||
val vars = mutableMapOf<String, Pair<UInt, DataType>>()
|
||||
for(reg in 0..15) {
|
||||
vars["cx16.r${reg}"] = (2+reg*2).toUInt() to DataType.UWORD // cx16.r0 .. cx16.r15
|
||||
vars["cx16.r${reg}s"] = (2+reg*2).toUInt() to DataType.WORD // cx16.r0s .. cx16.r15s
|
||||
vars["cx16.r${reg}L"] = (2+reg*2).toUInt() to DataType.UBYTE // cx16.r0L .. cx16.r15L
|
||||
vars["cx16.r${reg}H"] = (3+reg*2).toUInt() to DataType.UBYTE // cx16.r0H .. cx16.r15H
|
||||
vars["cx16.r${reg}sL"] = (2+reg*2).toUInt() to DataType.BYTE // cx16.r0sL .. cx16.r15sL
|
||||
vars["cx16.r${reg}sH"] = (3+reg*2).toUInt() to DataType.BYTE // cx16.r0sH .. cx16.r15sH
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
@@ -87,38 +99,4 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
"rmb", "smb", "stp", "wai")
|
||||
|
||||
|
||||
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.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(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package prog8.compiler.target.cx16
|
||||
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import prog8.compilerinterface.Zeropage
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
|
||||
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.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(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package prog8tests.asmgen
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import kotlin.math.max
|
||||
|
||||
object ProjectConfig : AbstractProjectConfig() {
|
||||
override val parallelism = max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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) = 0
|
||||
}
|
||||
|
||||
internal val DummyStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,18 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':compilerInterfaces')
|
||||
implementation project(':compilerAst')
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
<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="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
|
||||
@@ -197,6 +197,49 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
|
||||
if(leftBinExpr!=null && rightconst!=null) {
|
||||
if(expr.operator=="+" || expr.operator=="-") {
|
||||
if(leftBinExpr.operator in listOf("+", "-")) {
|
||||
val c2 = leftBinExpr.right.constValue(program)
|
||||
if(c2!=null) {
|
||||
// (X + C2) +/- rightConst --> X + (C2 +/- rightConst)
|
||||
// (X - C2) +/- rightConst --> X - (C2 +/- rightConst) mind the flipped right operator
|
||||
val operator = if(leftBinExpr.operator=="+") expr.operator else if(expr.operator=="-") "+" else "-"
|
||||
val constants = BinaryExpression(c2, operator, rightconst, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, leftBinExpr.operator, constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="*") {
|
||||
val c2 = leftBinExpr.right.constValue(program)
|
||||
if(c2!=null) {
|
||||
if(leftBinExpr.operator=="*") {
|
||||
// (X * C2) * rightConst --> X * (rightConst*C2)
|
||||
val constants = BinaryExpression(rightconst, "*", c2, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
} else if (leftBinExpr.operator=="/") {
|
||||
// (X / C2) * rightConst --> X * (rightConst/C2)
|
||||
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="/") {
|
||||
val c2 = leftBinExpr.right.constValue(program)
|
||||
if(c2!=null && leftBinExpr.operator=="/") {
|
||||
// (X / C1) / C2 --> X / (C1*C2)
|
||||
// NOTE: do not optimize (X * C1) / C2 --> X * (C1/C2) because this causes precision loss on integers
|
||||
val constants = BinaryExpression(c2, "*", rightconst, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "/", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator=="+" || expr.operator=="-") {
|
||||
if(leftBinExpr!=null && rightBinExpr!=null) {
|
||||
val c1 = leftBinExpr.right.constValue(program)
|
||||
@@ -231,7 +274,6 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
@@ -378,7 +420,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
{
|
||||
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS
|
||||
|
||||
// todo: this implements only a small set of possible reorderings at this time
|
||||
// todo: this implements only a small set of possible reorderings at this time, we could think of more
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the same.
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
|
||||
|
||||
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
|
||||
return when {
|
||||
// TODO assume (hope) cx16.r9 isn't used for anything else...
|
||||
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
|
||||
dt.istype(DataType.UBYTE) -> listOf("cx16", "r9L")
|
||||
dt.istype(DataType.BYTE) -> listOf("cx16", "r9sL")
|
||||
dt.istype(DataType.UWORD) -> listOf("cx16", "r9")
|
||||
|
||||
+13
-1
@@ -12,6 +12,18 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||
|
||||
dependencies {
|
||||
@@ -19,6 +31,7 @@ dependencies {
|
||||
implementation project(':codeOptimizers')
|
||||
implementation project(':compilerAst')
|
||||
implementation project(':codeGeneration')
|
||||
implementation 'org.antlr:antlr4-runtime:4.9.2'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
|
||||
@@ -40,7 +53,6 @@ configurations {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<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="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
@@ -19,5 +19,6 @@
|
||||
<orderEntry type="module" module-name="codeGeneration" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -15,7 +15,9 @@ c64 {
|
||||
|
||||
&ubyte COLOR = $0286 ; cursor color
|
||||
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||
&uword CINV = $0314 ; IRQ vector
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
; Cx16 specific disk drive I/O routines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
%import diskio
|
||||
|
||||
cx16diskio {
|
||||
|
||||
; Use kernal LOAD routine to load the given program file in memory.
|
||||
; This mimimics Basic's LOAD "filename",drive / LOAD "filename",drive,1
|
||||
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
|
||||
; If you specify a custom address_override, the first 2 bytes in the file are ignored
|
||||
; and the rest is loaded at the given location in memory.
|
||||
; Returns the number of bytes loaded (truncated to 16 bits, if the file is larger than 64 Kb,
|
||||
; you'll have to compensate yourself by checking the ram banks).
|
||||
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
|
||||
cx16.rambank(bank)
|
||||
uword size = diskio.load(drivenumber, filenameptr, address_override)
|
||||
if size
|
||||
return size + $2000 * (cx16.getrambank() - bank)
|
||||
return 0
|
||||
}
|
||||
|
||||
; Use kernal LOAD routine to load the given file in memory.
|
||||
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
|
||||
; This is different from Basic's LOAD instruction which always skips the first two bytes.
|
||||
; The load address is mandatory. Returns the number of bytes loaded.
|
||||
; Returns the number of bytes loaded (truncated to 16 bits, if the file is larger than 64 Kb,
|
||||
; you'll have to compensate yourself by checking the ram banks).
|
||||
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address) -> uword {
|
||||
cx16.rambank(bank)
|
||||
uword size = diskio.load_raw(drivenumber, filenameptr, address)
|
||||
if size
|
||||
return size + $2000 * (cx16.getrambank() - bank)
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
@@ -90,6 +90,8 @@ cx16 {
|
||||
|
||||
; irq and hardware vectors:
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
|
||||
@@ -312,7 +314,8 @@ romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||
|
||||
; TODO specify the correct clobbers for all functions below, we now assume all 3 regs are clobbered
|
||||
|
||||
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
|
||||
|
||||
; high level graphics & fonts
|
||||
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||
@@ -381,6 +384,20 @@ inline asmsub rambank(ubyte bank @A) {
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub getrombank() -> ubyte @A {
|
||||
; -- get the current rom bank
|
||||
%asm {{
|
||||
lda $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub getrambank() -> ubyte @A {
|
||||
; -- get the current ram bank
|
||||
%asm {{
|
||||
lda $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub numbanks() -> ubyte @A {
|
||||
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
|
||||
%asm {{
|
||||
|
||||
@@ -433,6 +433,16 @@ io_error:
|
||||
return first_byte
|
||||
}
|
||||
|
||||
; Use kernal LOAD routine to load the given program file in memory.
|
||||
; This mimimics Basic's LOAD "filename",drive / LOAD "filename",drive,1
|
||||
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
|
||||
; If you specify a custom address_override, the first 2 bytes in the file are ignored
|
||||
; and the rest is loaded at the given location in memory.
|
||||
; Returns the number of bytes loaded.
|
||||
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
||||
; (which is possible on the Commander X16), the returned size is not correct,
|
||||
; because it doesn't take the number of ram banks into account.
|
||||
; Consider using cx16diskio.load() instead.
|
||||
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
ubyte secondary = 1
|
||||
@@ -456,13 +466,32 @@ io_error:
|
||||
c64.CLOSE(1)
|
||||
|
||||
if end_of_load
|
||||
return end_of_load - address_override
|
||||
return end_of_load - address_override ; not correct when the file spans multiple RAM banks
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
str filename = "0:??????????????????????????????????????"
|
||||
; Use kernal LOAD routine to load the given file in memory.
|
||||
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
|
||||
; This is different from Basic's LOAD instruction which always skips the first two bytes.
|
||||
; The load address is mandatory. Returns the number of bytes loaded.
|
||||
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
||||
; (which is possible on the Commander X16), the returned size is not correct,
|
||||
; because it doesn't take the number of ram banks into account.
|
||||
; Consider using cx16diskio.load_raw() instead.
|
||||
sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword {
|
||||
if not f_open(drivenumber, filenameptr)
|
||||
return 0
|
||||
uword size = f_read(address, 2)
|
||||
f_close()
|
||||
if size!=2
|
||||
return 0
|
||||
address += 2
|
||||
size = load(drivenumber, filenameptr, address)
|
||||
if size
|
||||
return size+2
|
||||
return 0
|
||||
}
|
||||
|
||||
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||
; -- delete a file on the drive
|
||||
@@ -489,4 +518,6 @@ io_error:
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
}
|
||||
|
||||
str filename = "0:??????????????????????????????????????"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
7.4
|
||||
7.5
|
||||
@@ -81,7 +81,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val args = CompilerArguments(
|
||||
val compilerArgs = CompilerArguments(
|
||||
filepath,
|
||||
dontOptimize != true,
|
||||
optimizeFloatExpressions == true,
|
||||
@@ -92,7 +92,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
val compilationResult = compileProgram(args)
|
||||
val compilationResult = compileProgram(compilerArgs)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
try {
|
||||
val args = CompilerArguments(
|
||||
val compilerArgs = CompilerArguments(
|
||||
filepath,
|
||||
dontOptimize != true,
|
||||
optimizeFloatExpressions == true,
|
||||
@@ -140,7 +140,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
compilationResult = compileProgram(args)
|
||||
compilationResult = compileProgram(compilerArgs)
|
||||
if (!compilationResult.success)
|
||||
return false
|
||||
} catch (x: AstException) {
|
||||
|
||||
@@ -31,6 +31,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
varsList.add(decl.name to decl)
|
||||
}
|
||||
|
||||
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
// move all subroutines to the bottom of the block
|
||||
val subs = block.statements.filterIsInstance<Subroutine>()
|
||||
block.statements.removeAll(subs)
|
||||
block.statements.addAll(subs)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -58,8 +66,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||
// use the other part of the expression to split.
|
||||
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
|
||||
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), sourceDt, implicit=true)
|
||||
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true)
|
||||
val assignRight = Assignment(assignment.target, right, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||
@@ -67,8 +75,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
} else {
|
||||
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
|
||||
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), sourceDt, implicit=true)
|
||||
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true)
|
||||
val assignLeft = Assignment(assignment.target, left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
@@ -116,12 +124,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val outerScope = subroutine.definingScope
|
||||
val outerStatements = outerScope.statements
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if (subroutineStmtIdx > 0
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Jump
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
||||
&& outerScope !is Block) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||
if (subroutineStmtIdx > 0) {
|
||||
val prevStmt = outerStatements[subroutineStmtIdx-1]
|
||||
if(outerScope !is Block
|
||||
&& (prevStmt !is Jump || prevStmt.isGosub)
|
||||
&& prevStmt !is Subroutine
|
||||
&& prevStmt !is Return) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
@@ -219,6 +229,9 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
|
||||
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
|
||||
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
|
||||
// TODO: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
|
||||
// TODO: this should be replaced by a general expression-evaluation optimization step.
|
||||
// the actual conditional expression in the statement should be no more than VARIABLE <COMPARISON-OPERATOR> SIMPLE-EXPRESSION
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
var leftOperandReplacement: Expression? = null
|
||||
@@ -317,6 +330,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
throw FatalAstException("0==X should have been swapped to if X==0")
|
||||
|
||||
// TODO simplify the conditional expression, introduce simple assignments if required.
|
||||
// make sure to evaluate it only once, but also right at entry of the while loop
|
||||
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
|
||||
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
|
||||
// NOTE: this is nasty for a while-statement as the condition occurs at the top of the loop
|
||||
@@ -408,10 +422,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "retval_interm_ub" else "retval_interm_b"
|
||||
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val target = AssignTarget(IdentifierReference(listOf("prog8_lib", register), expr.indexer.position), null, null, expr.indexer.position)
|
||||
val tempvar = if(dt.isBytes) listOf("prog8_lib","retval_interm_ub") else listOf("prog8_lib","retval_interm_b")
|
||||
val target = AssignTarget(IdentifierReference(tempvar, expr.indexer.position), null, null, expr.indexer.position)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
|
||||
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
|
||||
|
||||
@@ -80,8 +80,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
|
||||
// printProgram(program)
|
||||
|
||||
if (args.writeAssembly) {
|
||||
val result = writeAssembly(program, args.errors, args.outputDir, args.quietAssembler, compilationOptions)
|
||||
when (result) {
|
||||
when (val result = writeAssembly(program, args.errors, args.outputDir, args.quietAssembler, compilationOptions)) {
|
||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||
is WriteAssemblyResult.Fail -> {
|
||||
System.err.println(result.error)
|
||||
@@ -257,7 +256,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
|
||||
// perform initial syntax checks and processings
|
||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||
program.preprocessAst(program)
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
program.checkIdentifiers(errors, program, compilerOptions)
|
||||
errors.report()
|
||||
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
|
||||
// ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
|
||||
@@ -274,7 +273,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
|
||||
errors.report()
|
||||
program.checkValid(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
program.checkIdentifiers(errors, program, compilerOptions)
|
||||
errors.report()
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ internal class ErrorReporter: IErrorReporter {
|
||||
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()
|
||||
val msg = "${it.severity} ${it.position.toClickableStr()} ${it.message}".trim()
|
||||
if(msg !in alreadyReportedMessages) {
|
||||
printer.println(msg)
|
||||
alreadyReportedMessages.add(msg)
|
||||
|
||||
@@ -43,10 +43,7 @@ class ModuleImporter(private val program: Program,
|
||||
println(logMsg)
|
||||
|
||||
val module = importModule(SourceCode.File(srcPath))
|
||||
return if(module==null)
|
||||
Err(NoSuchFileException(srcPath.toFile()))
|
||||
else
|
||||
Ok(module)
|
||||
return Ok(module)
|
||||
}
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
@@ -56,7 +53,7 @@ class ModuleImporter(private val program: Program,
|
||||
return executeImportDirective(import, null)
|
||||
}
|
||||
|
||||
private fun importModule(src: SourceCode) : Module? {
|
||||
private fun importModule(src: SourceCode) : Module {
|
||||
val moduleAst = Prog8Parser.parseModule(src)
|
||||
program.addModule(moduleAst)
|
||||
|
||||
@@ -113,8 +110,7 @@ class ModuleImporter(private val program: Program,
|
||||
}
|
||||
)
|
||||
|
||||
if(importedModule!=null)
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
return importedModule
|
||||
}
|
||||
|
||||
@@ -150,7 +146,7 @@ class ModuleImporter(private val program: Program,
|
||||
locations.forEach {
|
||||
try {
|
||||
return Ok(SourceCode.File(it.resolve(fileName)))
|
||||
} catch (e: NoSuchFileException) {
|
||||
} catch (_: NoSuchFileException) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.CharConversionException
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
|
||||
internal class AstChecker(private val program: Program,
|
||||
@@ -218,7 +217,7 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
private fun hasReturnOrJump(scope: IStatementContainer): Boolean {
|
||||
private fun hasReturnOrJumpOrRts(scope: IStatementContainer): Boolean {
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
var count=0
|
||||
@@ -230,6 +229,13 @@ internal class AstChecker(private val program: Program,
|
||||
if(!jump.isGosub)
|
||||
count++
|
||||
}
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
val assembly = inlineAssembly.assembly
|
||||
if(" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly )
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
val s=Searcher()
|
||||
@@ -263,13 +269,11 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
// subroutine must contain at least one 'return' or 'goto'
|
||||
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
|
||||
if(!hasReturnOrJump(subroutine)) {
|
||||
if (subroutine.amountOfRtsInAsm() == 0) {
|
||||
if (subroutine.returntypes.isNotEmpty()) {
|
||||
// for asm subroutines with an address, no statement check is possible.
|
||||
if (subroutine.asmAddress == null && !subroutine.inline)
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or rts/jmp/bra in case of %asm)")
|
||||
}
|
||||
if(!hasReturnOrJumpOrRts(subroutine)) {
|
||||
if (subroutine.returntypes.isNotEmpty()) {
|
||||
// for asm subroutines with an address, no statement check is possible.
|
||||
if (subroutine.asmAddress == null && !subroutine.inline)
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or rts/jmp/bra in case of %asm)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,13 +387,13 @@ internal class AstChecker(private val program: Program,
|
||||
if(statusFlagsNoCarry.isNotEmpty())
|
||||
err("can only use Carry as status flag parameter")
|
||||
|
||||
} else {
|
||||
// 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).
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
||||
// Non-string and non-ubytearray Pass-by-reference datatypes can not occur as parameters to a subroutine directly
|
||||
// Instead, their reference (address) should be passed (as an UWORD).
|
||||
for(p in subroutine.parameters) {
|
||||
if(p.type in PassByReferenceDatatypes && p.type !in listOf(DataType.STR, DataType.ARRAY_UB)) {
|
||||
errors.err("this pass-by-reference type can't be used as a parameter type. Instead, use an uword to receive the address, or access the variable from the outer scope directly.", p.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -414,18 +418,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
if(assignment.value is FunctionCall) {
|
||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
|
||||
if (stmt is Subroutine) {
|
||||
val idt = assignment.target.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("assignment target invalid dt")
|
||||
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.getOr(DataType.BYTE))) {
|
||||
errors.err("return type mismatch: ${stmt.returntypes.single()} expected $idt", assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val valueDt = assignment.value.inferType(program)
|
||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||
@@ -500,7 +492,7 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
|
||||
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
|
||||
sourceDatatype.getOr(DataType.BYTE), assignment.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -716,7 +708,7 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive can't be used here")
|
||||
val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]"
|
||||
if(directive.args.isEmpty()) err(errormsg)
|
||||
else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
|
||||
else if(directive.args[0].str==null) err(errormsg)
|
||||
else if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg)
|
||||
else if(directive.args.size==3 && directive.args[2].int==null) err(errormsg)
|
||||
else if(directive.args.size>3) err(errormsg)
|
||||
@@ -1015,8 +1007,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
}
|
||||
|
||||
val error =
|
||||
VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
|
||||
if(error!=null) {
|
||||
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
|
||||
}
|
||||
@@ -1230,7 +1221,7 @@ internal class AstChecker(private val program: Program,
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>256)
|
||||
if(arraySpecSize>256)
|
||||
return err("byte array length must be 1-256")
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
@@ -1249,7 +1240,7 @@ internal class AstChecker(private val program: Program,
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>128)
|
||||
if(arraySpecSize>128)
|
||||
return err("word array length must be 1-128")
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
@@ -1268,7 +1259,7 @@ internal class AstChecker(private val program: Program,
|
||||
val arraySize = value.value.size
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize < 1 || arraySpecSize>51)
|
||||
if(arraySpecSize>51)
|
||||
return err("float array length must be 1-51")
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
@@ -1372,8 +1363,8 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
private fun checkAssignmentCompatible(targetDatatype: DataType,
|
||||
sourceDatatype: DataType,
|
||||
sourceValue: Expression,
|
||||
position: Position) : Boolean {
|
||||
sourceValue: Expression) : Boolean {
|
||||
val position = sourceValue.position
|
||||
|
||||
if(sourceValue is RangeExpr)
|
||||
errors.err("can't assign a range value to something else", position)
|
||||
@@ -1401,15 +1392,9 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("cannot assign float to ${targetDatatype.name.lowercase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
|
||||
else {
|
||||
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
|
||||
errors.err(
|
||||
"cannot assign ${sourceDatatype.name.lowercase()} to ${
|
||||
targetDatatype.name.lowercase(
|
||||
Locale.getDefault()
|
||||
)
|
||||
}", position)
|
||||
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
@@ -73,9 +72,9 @@ internal fun Program.preprocessAst(program: Program) {
|
||||
mods = transforms.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, program: Program, options: CompilationOptions) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(errors, options.compTarget)
|
||||
val checker2 = AstIdentifiersChecker(errors, program, options.compTarget)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.noErrors()) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
@@ -8,7 +13,9 @@ import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
internal class AstIdentifiersChecker(private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
|
||||
internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
private val program: Program,
|
||||
private val compTarget: ICompilationTarget) : IAstVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
|
||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||
@@ -121,4 +128,33 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, private
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall) = visitFunctionCall(functionCall)
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
||||
|
||||
private fun visitFunctionCall(call: IFunctionCall) {
|
||||
when (val target = call.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
if(call.args.size != target.parameters.size) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
errors.err("invalid number of arguments", pos)
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
if(call.args.size != func.parameters.size) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
errors.err("invalid number of arguments", pos)
|
||||
}
|
||||
}
|
||||
is Label -> {
|
||||
if(call.args.isNotEmpty()) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
errors.err("cannot use arguments when calling a label", pos)
|
||||
}
|
||||
}
|
||||
null -> {}
|
||||
else -> throw FatalAstException("weird call target")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
@@ -120,23 +119,28 @@ internal class StatementReorderer(val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
val subs = subroutine.statements.filterIsInstance<Subroutine>()
|
||||
if(subs.isNotEmpty()) {
|
||||
// all subroutines defined within this subroutine are moved to the end
|
||||
return subs.map { IAstModification.Remove(it, subroutine) } +
|
||||
subs.map { IAstModification.InsertLast(it, subroutine) }
|
||||
// NOTE: this doesn't check if this has already been done!!!
|
||||
modifications +=
|
||||
subs.map { IAstModification.Remove(it, subroutine) } +
|
||||
subs.map { IAstModification.InsertLast(it, subroutine) }
|
||||
}
|
||||
|
||||
if(!subroutine.isAsmSubroutine) {
|
||||
// change 'str' parameters into 'uword' (just treat it as an address)
|
||||
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
|
||||
val parameterChanges = stringParams.map {
|
||||
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
|
||||
IAstModification.ReplaceNode(it, uwordParam, subroutine)
|
||||
}
|
||||
// change 'str' and 'ubyte[]' parameters into 'uword' (just treat it as an address)
|
||||
val stringParams = subroutine.parameters.filter { it.type==DataType.STR || it.type==DataType.ARRAY_UB }
|
||||
val parameterChanges = stringParams.map {
|
||||
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
|
||||
IAstModification.ReplaceNode(it, uwordParam, subroutine)
|
||||
}
|
||||
|
||||
val varsChanges = mutableListOf<IAstModification>()
|
||||
if(!subroutine.isAsmSubroutine) {
|
||||
val stringParamsByNames = stringParams.associateBy { it.name }
|
||||
val varsChanges =
|
||||
varsChanges +=
|
||||
if(stringParamsByNames.isNotEmpty()) {
|
||||
subroutine.statements
|
||||
.filterIsInstance<VarDecl>()
|
||||
@@ -147,11 +151,9 @@ internal class StatementReorderer(val program: Program,
|
||||
}
|
||||
}
|
||||
else emptyList()
|
||||
|
||||
return parameterChanges + varsChanges
|
||||
}
|
||||
|
||||
return noModifications
|
||||
return modifications + parameterChanges + varsChanges
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
@@ -254,6 +256,13 @@ internal class StatementReorderer(val program: Program,
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
val lastChoiceValues = whenStatement.choices.lastOrNull()?.values
|
||||
if(lastChoiceValues?.isNotEmpty()==true) {
|
||||
val elseChoice = whenStatement.choices.indexOfFirst { it.values==null || it.values?.isEmpty()==true }
|
||||
if(elseChoice>=0)
|
||||
errors.err("else choice must be the last one", whenStatement.choices[elseChoice].position)
|
||||
}
|
||||
|
||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||
it.first?.first() ?: Int.MAX_VALUE
|
||||
}
|
||||
@@ -390,7 +399,8 @@ internal class StatementReorderer(val program: Program,
|
||||
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(argDt in ArrayDatatypes) {
|
||||
// pass the address of the array instead
|
||||
argumentValue = AddressOf(argumentValue as IdentifierReference, argumentValue.position)
|
||||
if(argumentValue is IdentifierReference)
|
||||
argumentValue = AddressOf(argumentValue, argumentValue.position)
|
||||
}
|
||||
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position)
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
}
|
||||
val cvalue = assignment.value.constValue(program)
|
||||
if(cvalue!=null) {
|
||||
val number = cvalue.number.toDouble()
|
||||
val number = cvalue.number
|
||||
// more complex comparisons if the type is different, but the constant value is compatible
|
||||
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
|
||||
if(number>0)
|
||||
@@ -151,10 +151,12 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
val requiredType = pair.first.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
||||
call as Node)
|
||||
// don't need a cast for pass-by-reference types that are assigned to UWORD
|
||||
if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
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, UNLESS it's a str parameter in the containing subroutine
|
||||
|
||||
@@ -11,7 +11,7 @@ import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
|
||||
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val error = checkTypes(functionCall as IFunctionCall, program)
|
||||
@@ -33,7 +33,9 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
// there are some exceptions that are considered compatible, such as STR <> UWORD
|
||||
if(argDt==DataType.STR && paramDt==DataType.UWORD ||
|
||||
argDt==DataType.UWORD && paramDt==DataType.STR)
|
||||
argDt==DataType.UWORD && paramDt==DataType.STR ||
|
||||
argDt==DataType.UWORD && paramDt==DataType.ARRAY_UB ||
|
||||
argDt==DataType.STR && paramDt==DataType.ARRAY_UB)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
@@ -8,11 +8,6 @@ import prog8.compiler.ModuleImporter
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.SourceCode
|
||||
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 io.kotest.assertions.fail
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
@@ -21,6 +16,11 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeIn
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
|
||||
|
||||
class TestModuleImporter: FunSpec({
|
||||
@@ -103,7 +103,7 @@ class TestModuleImporter: FunSpec({
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
|
||||
val module = importer.importModule(path.absolute()).getOrElse { throw it }
|
||||
@@ -117,7 +117,7 @@ class TestModuleImporter: FunSpec({
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
withClue("sanity check: path should NOT be absolute") {
|
||||
path.isAbsolute shouldBe false
|
||||
@@ -134,7 +134,7 @@ class TestModuleImporter: FunSpec({
|
||||
.div(workingDir.relativize(fixturesDir))
|
||||
.invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = Path(".", fileName)
|
||||
assumeReadableFile(searchIn, path)
|
||||
|
||||
@@ -148,7 +148,7 @@ class TestModuleImporter: FunSpec({
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(srcPath) }
|
||||
|
||||
@@ -173,14 +173,14 @@ class TestModuleImporter: FunSpec({
|
||||
val act = { importer.importModule(importing) }
|
||||
|
||||
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>() { act() }.let {
|
||||
shouldThrow<ParseError> { act() }.let {
|
||||
it.position.file shouldBe SourceCode.relative(imported).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
}
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
}
|
||||
}
|
||||
@@ -209,14 +209,14 @@ class TestModuleImporter: FunSpec({
|
||||
val result = importer.importLibraryModule(filenameNoExt)
|
||||
withClue(count[n] + " call / NO .p8 extension") { result shouldBe null }
|
||||
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
|
||||
errors.errors.single() shouldContain "0:0) no module found with name i_do_not_exist"
|
||||
errors.report()
|
||||
program.modules.size shouldBe 1
|
||||
|
||||
val result2 = importer.importLibraryModule(filenameWithExt)
|
||||
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
|
||||
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
|
||||
errors.errors.single() shouldContain "0:0) no module found with name i_do_not_exist.p8"
|
||||
errors.report()
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
@@ -228,7 +228,7 @@ class TestModuleImporter: FunSpec({
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>()
|
||||
|
||||
@@ -7,7 +7,7 @@ import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8tests.ast.helpers.*
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
|
||||
@@ -10,7 +10,7 @@ import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Label
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.ast.helpers.*
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileFile
|
||||
|
||||
@@ -10,12 +10,11 @@ import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compilerinterface.size
|
||||
import prog8.compilerinterface.toConstantIntegerRange
|
||||
import prog8tests.ast.helpers.cartesianProduct
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
@@ -229,8 +228,8 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
""", errors, false).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain ".p8:5:29: range expression from value must be integer"
|
||||
errors.errors[1] shouldContain ".p8:5:44: range expression to value must be integer"
|
||||
errors.errors[0] shouldContain ".p8:5:30) range expression from value must be integer"
|
||||
errors.errors[1] shouldContain ".p8:5:45) range expression to value must be integer"
|
||||
}
|
||||
|
||||
test("testForLoopWithIterable_str") {
|
||||
|
||||
@@ -5,10 +5,7 @@ import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.ast.helpers.assumeReadableFile
|
||||
import prog8tests.ast.helpers.fixturesDir
|
||||
import prog8tests.ast.helpers.outputDir
|
||||
import prog8tests.ast.helpers.workingDir
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
@@ -73,19 +70,19 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
}
|
||||
|
||||
test("testAbsoluteFilePathOutsideWorkingDir") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
compileFile(filepath.absolute(), listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
test("testFilePathOutsideWorkingDirRelativeToWorkingDir") {
|
||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "simple_main.p8").absolute())
|
||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "ast_simple_main.p8").absolute())
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val sourcedirs = listOf("$fixturesDir")
|
||||
compileFile(filepath.fileName, sourcedirs)
|
||||
.assertSuccess()
|
||||
|
||||
@@ -9,10 +9,10 @@ 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
|
||||
|
||||
|
||||
class TestImportedModulesOrderAndOptions: FunSpec({
|
||||
|
||||
@@ -5,9 +5,7 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.doubles.plusOrMinus
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||
import prog8.compiler.target.cbm.Mflpt5
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
|
||||
|
||||
@@ -69,8 +67,8 @@ class TestNumbers: FunSpec({
|
||||
}
|
||||
|
||||
test("testFloatRange") {
|
||||
Mflpt5.fromNumber(FLOAT_MAX_POSITIVE) shouldBe Mflpt5(0xffu, 0x7fu, 0xffu, 0xffu, 0xffu)
|
||||
Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xffu, 0xffu, 0xffu, 0xffu, 0xffu)
|
||||
Mflpt5.fromNumber(Mflpt5.FLOAT_MAX_POSITIVE) shouldBe Mflpt5(0xffu, 0x7fu, 0xffu, 0xffu, 0xffu)
|
||||
Mflpt5.fromNumber(Mflpt5.FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xffu, 0xffu, 0xffu, 0xffu, 0xffu)
|
||||
Mflpt5.fromNumber(1.7e-38) shouldBe Mflpt5(0x03u, 0x39u, 0x1du, 0x15u, 0x63u)
|
||||
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03u, 0xb9u, 0x1du, 0x15u, 0x63u)
|
||||
|
||||
@@ -106,6 +106,120 @@ class TestOptimization: FunSpec({
|
||||
constvalue.parent shouldBeSameInstanceAs pfx.parent
|
||||
}
|
||||
|
||||
test("const folding multiple scenarios +/-") {
|
||||
val source = """
|
||||
main {
|
||||
const ubyte boardHeightC = 20
|
||||
const ubyte boardOffsetC = 3
|
||||
|
||||
sub start() {
|
||||
uword load_location = 12345
|
||||
word llw = 12345
|
||||
cx16.r0 = load_location + 8000 + 1000 + 1000
|
||||
cx16.r2 = 8000 + 1000 + 1000 + load_location
|
||||
cx16.r4 = load_location + boardOffsetC + boardHeightC - 1
|
||||
cx16.r5s = llw - 900 - 999
|
||||
cx16.r7s = llw - 900 + 999
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, true, source, writeAssembly = false).assertSuccess()
|
||||
// expected:
|
||||
// uword load_location
|
||||
// load_location = 12345
|
||||
// word llw
|
||||
// llw = 12345
|
||||
// cx16.r0 = load_location
|
||||
// cx16.r0 += 10000
|
||||
// cx16.r2 = load_location
|
||||
// cx16.r2 += 10000
|
||||
// cx16.r4 = load_location
|
||||
// cx16.r4 += 22
|
||||
// cx16.r5s = llw
|
||||
// cx16.r5s -= 1899
|
||||
// cx16.r7s = llw
|
||||
// cx16.r7s += 99
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 14
|
||||
|
||||
val addR0value = (stmts[5] as Assignment).value
|
||||
val binexpr0 = addR0value as BinaryExpression
|
||||
binexpr0.operator shouldBe "+"
|
||||
binexpr0.right shouldBe NumericLiteralValue(DataType.UWORD, 10000.0, Position.DUMMY)
|
||||
val addR2value = (stmts[7] as Assignment).value
|
||||
val binexpr2 = addR2value as BinaryExpression
|
||||
binexpr2.operator shouldBe "+"
|
||||
binexpr2.right shouldBe NumericLiteralValue(DataType.UWORD, 10000.0, Position.DUMMY)
|
||||
val addR4value = (stmts[9] as Assignment).value
|
||||
val binexpr4 = addR4value as BinaryExpression
|
||||
binexpr4.operator shouldBe "+"
|
||||
binexpr4.right shouldBe NumericLiteralValue(DataType.UWORD, 22.0, Position.DUMMY)
|
||||
val subR5value = (stmts[11] as Assignment).value
|
||||
val binexpr5 = subR5value as BinaryExpression
|
||||
binexpr5.operator shouldBe "-"
|
||||
binexpr5.right shouldBe NumericLiteralValue(DataType.UWORD, 1899.0, Position.DUMMY)
|
||||
val subR7value = (stmts[13] as Assignment).value
|
||||
val binexpr7 = subR7value as BinaryExpression
|
||||
binexpr7.operator shouldBe "+"
|
||||
binexpr7.right shouldBe NumericLiteralValue(DataType.UWORD, 99.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("const folding multiple scenarios * and /") {
|
||||
val source = """
|
||||
main {
|
||||
sub start() {
|
||||
word llw = 300
|
||||
cx16.r0s = 9 * 2 * 10 * llw
|
||||
cx16.r1s = llw * 9 * 2 * 10
|
||||
cx16.r2s = llw / 30 / 3
|
||||
cx16.r3s = llw / 2 * 10
|
||||
cx16.r4s = llw * 90 / 5 ; not optimized because of loss of integer division precision
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, true, source, writeAssembly = false).assertSuccess()
|
||||
printProgram(result.program)
|
||||
// expected:
|
||||
// word llw
|
||||
// llw = 300
|
||||
// cx16.r0s = llw
|
||||
// cx16.r0s *= 180
|
||||
// cx16.r1s = llw
|
||||
// cx16.r1s *= 180
|
||||
// cx16.r2s = llw
|
||||
// cx16.r2s /= 90
|
||||
// cx16.r3s = llw
|
||||
// cx16.r3s *= 5
|
||||
// cx16.r4s = llw
|
||||
// cx16.r4s *= 90
|
||||
// cx16.r4s /= 5
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 13
|
||||
|
||||
val mulR0Value = (stmts[3] as Assignment).value
|
||||
val binexpr0 = mulR0Value as BinaryExpression
|
||||
binexpr0.operator shouldBe "*"
|
||||
binexpr0.right shouldBe NumericLiteralValue(DataType.UWORD, 180.0, Position.DUMMY)
|
||||
val mulR1Value = (stmts[5] as Assignment).value
|
||||
val binexpr1 = mulR1Value as BinaryExpression
|
||||
binexpr1.operator shouldBe "*"
|
||||
binexpr1.right shouldBe NumericLiteralValue(DataType.UWORD, 180.0, Position.DUMMY)
|
||||
val divR2Value = (stmts[7] as Assignment).value
|
||||
val binexpr2 = divR2Value as BinaryExpression
|
||||
binexpr2.operator shouldBe "/"
|
||||
binexpr2.right shouldBe NumericLiteralValue(DataType.UWORD, 90.0, Position.DUMMY)
|
||||
val mulR3Value = (stmts[9] as Assignment).value
|
||||
val binexpr3 = mulR3Value as BinaryExpression
|
||||
binexpr3.operator shouldBe "*"
|
||||
binexpr3.right shouldBe NumericLiteralValue(DataType.UWORD, 5.0, Position.DUMMY)
|
||||
val mulR4Value = (stmts[11] as Assignment).value
|
||||
val binexpr4 = mulR4Value as BinaryExpression
|
||||
binexpr4.operator shouldBe "*"
|
||||
binexpr4.right shouldBe NumericLiteralValue(DataType.UWORD, 90.0, Position.DUMMY)
|
||||
val divR4Value = (stmts[12] as Assignment).value
|
||||
val binexpr4b = divR4Value as BinaryExpression
|
||||
binexpr4b.operator shouldBe "/"
|
||||
binexpr4b.right shouldBe NumericLiteralValue(DataType.UWORD, 5.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("constantfolded and silently typecasted for initializervalues") {
|
||||
val sourcecode = """
|
||||
main {
|
||||
@@ -118,7 +232,7 @@ class TestOptimization: FunSpec({
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val mainsub = result.program.entrypoint
|
||||
mainsub.statements.size shouldBe 10
|
||||
val declTest = mainsub.statements[0] as VarDecl
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
@@ -47,10 +48,10 @@ class TestSubroutines: FunSpec({
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.STR
|
||||
asmfunc.statements.isEmpty() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
withClue("str param for normal subroutine should be changed into UWORD") {
|
||||
withClue("str param for subroutines should be changed into UWORD") {
|
||||
asmfunc.parameters.single().type shouldBe DataType.UWORD
|
||||
func.parameters.single().type shouldBe DataType.UWORD
|
||||
func.statements.size shouldBe 4
|
||||
val paramvar = func.statements[0] as VarDecl
|
||||
@@ -99,10 +100,10 @@ class TestSubroutines: FunSpec({
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.STR
|
||||
asmfunc.statements.single() shouldBe instanceOf<Return>()
|
||||
func.isAsmSubroutine shouldBe false
|
||||
withClue("asmgen should have changed str to uword type") {
|
||||
withClue("str param should have been changed to uword") {
|
||||
asmfunc.parameters.single().type shouldBe DataType.UWORD
|
||||
func.parameters.single().type shouldBe DataType.UWORD
|
||||
}
|
||||
asmfunc.statements.last() shouldBe instanceOf<Return>()
|
||||
@@ -128,49 +129,24 @@ class TestSubroutines: FunSpec({
|
||||
(call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing"
|
||||
}
|
||||
|
||||
test("array param not yet allowd (but should perhaps be?)") {
|
||||
// note: the *parser* accepts this as it is valid *syntax*,
|
||||
// however, it's not (yet) valid for the compiler
|
||||
val text = """
|
||||
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?
|
||||
errors.warnings.size shouldBe 0
|
||||
errors.errors.single() shouldContain ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly"
|
||||
}
|
||||
|
||||
// TODO allow this?
|
||||
xtest("arrayParameter") {
|
||||
test("ubyte[] array parameters") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] array = [1,2,3]
|
||||
|
||||
asmfunc(array)
|
||||
asmfunc([4,5,6])
|
||||
asmfunc($2000)
|
||||
asmfunc(12.345)
|
||||
asmfunc("zzzz")
|
||||
func(array)
|
||||
func([4,5,6])
|
||||
func($2000)
|
||||
func(12.345)
|
||||
func("zzzz")
|
||||
}
|
||||
|
||||
asmsub asmfunc(ubyte[] thing @AY) {
|
||||
}
|
||||
|
||||
sub func(ubyte[22] thing) {
|
||||
sub func(ubyte[] thing) {
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -180,12 +156,31 @@ class TestSubroutines: FunSpec({
|
||||
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"}
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.ARRAY_UB
|
||||
asmfunc.statements.isEmpty() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
func.parameters.single().type shouldBe DataType.ARRAY_UB
|
||||
func.statements.isEmpty() shouldBe true
|
||||
withClue("ubyte array param should have been replaced by UWORD pointer") {
|
||||
asmfunc.parameters.single().type shouldBe DataType.UWORD
|
||||
func.parameters.single().type shouldBe DataType.UWORD
|
||||
}
|
||||
}
|
||||
|
||||
test("not ubyte[] array parameters not allowed") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
|
||||
asmsub func1(uword[] thing @AY) {
|
||||
}
|
||||
|
||||
sub func(byte[] thing) {
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "pass-by-reference type can't be used"
|
||||
errors.errors[1] shouldContain "pass-by-reference type can't be used"
|
||||
}
|
||||
|
||||
test("uword param and normal varindexed as array work as DirectMemoryRead") {
|
||||
@@ -262,4 +257,86 @@ class TestSubroutines: FunSpec({
|
||||
addressExpr.operator shouldBe "+"
|
||||
(addressExpr.right as NumericLiteralValue).number.toInt() shouldBe 10
|
||||
}
|
||||
|
||||
test("invalid number of args check on normal subroutine") {
|
||||
val text="""
|
||||
main {
|
||||
sub thing(ubyte a1, ubyte a2) {
|
||||
}
|
||||
|
||||
sub start() {
|
||||
thing(1)
|
||||
thing(1,2)
|
||||
thing(1,2,3)
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "7:25) invalid number of arguments"
|
||||
errors.errors[1] shouldContain "9:25) invalid number of arguments"
|
||||
}
|
||||
|
||||
test("invalid number of args check on asm subroutine") {
|
||||
val text="""
|
||||
main {
|
||||
asmsub thing(ubyte a1 @A, ubyte a2 @Y) {
|
||||
}
|
||||
|
||||
sub start() {
|
||||
thing(1)
|
||||
thing(1,2)
|
||||
thing(1,2,3)
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "7:25) invalid number of arguments"
|
||||
errors.errors[1] shouldContain "9:25) invalid number of arguments"
|
||||
}
|
||||
|
||||
test("invalid number of args check on call to label and builtin func") {
|
||||
val text="""
|
||||
main {
|
||||
label:
|
||||
sub start() {
|
||||
label()
|
||||
label(1)
|
||||
void rnd()
|
||||
void rnd(1)
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "cannot use arguments"
|
||||
errors.errors[1] shouldContain "invalid number of arguments"
|
||||
}
|
||||
|
||||
test("fallthrough prevented") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
func(1)
|
||||
|
||||
sub func(ubyte a) {
|
||||
a++
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
|
||||
stmts.last() shouldBe instanceOf<Subroutine>()
|
||||
stmts.dropLast(1).last() shouldBe instanceOf<Return>() // this prevents the fallthrough
|
||||
stmts.dropLast(2).last() shouldBe instanceOf<Jump>()
|
||||
}
|
||||
})
|
||||
@@ -13,8 +13,8 @@ import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
|
||||
import prog8.compiler.target.c64.C64Zeropage
|
||||
import prog8.compiler.target.cx16.CX16Zeropage
|
||||
import prog8.compilerinterface.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ import prog8.ast.internedStringsModuleName
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.ast.helpers.DummyFunctions
|
||||
import prog8tests.ast.helpers.DummyMemsizer
|
||||
import prog8tests.ast.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
||||
|
||||
class TestAstToSourceText: AnnotationSpec() {
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
@@ -18,12 +19,14 @@ import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.ast.helpers.*
|
||||
import prog8tests.ast.helpers.DummyFunctions
|
||||
import prog8tests.ast.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.name
|
||||
@@ -186,7 +189,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("from an empty file should result in empty Module") {
|
||||
val path = assumeReadableFile(fixturesDir, "empty.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_empty.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
module.statements.size shouldBe 0
|
||||
}
|
||||
@@ -205,7 +208,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("parsed from a file") {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
module.name shouldBe path.nameWithoutExtension
|
||||
}
|
||||
@@ -268,7 +271,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("in ParseError from bad file source code") {
|
||||
val path = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
val e = shouldThrow<ParseError> { parseModule(SourceCode.File(path)) }
|
||||
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6)
|
||||
@@ -284,21 +287,21 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("of Module parsed from a file") {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
|
||||
}
|
||||
|
||||
test("of non-root Nodes parsed from file") {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
val mpf = module.position.file
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
|
||||
val mainBlock = module.statements.filterIsInstance<Block>()[0]
|
||||
assertPositionOf(mainBlock, mpf, 2, 0, 3)
|
||||
assertPositionOf(mainBlock, mpf, 2, 1, 4)
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
|
||||
assertPositionOf(startSub, mpf, 3, 4, 6)
|
||||
assertPositionOf(startSub, mpf, 3, 5, 7)
|
||||
}
|
||||
|
||||
test("of non-root Nodes parsed from a string") {
|
||||
@@ -320,22 +323,22 @@ class TestProg8Parser: FunSpec( {
|
||||
val mpf = module.position.file
|
||||
|
||||
val targetDirective = module.statements.filterIsInstance<Directive>()[0]
|
||||
assertPositionOf(targetDirective, mpf, 1, 0, 8)
|
||||
assertPositionOf(targetDirective, mpf, 1, 1, 9)
|
||||
val mainBlock = module.statements.filterIsInstance<Block>()[0]
|
||||
assertPositionOf(mainBlock, mpf, 2, 0, 3)
|
||||
assertPositionOf(mainBlock, mpf, 2, 1, 4)
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
|
||||
assertPositionOf(startSub, mpf, 3, 4, 6)
|
||||
assertPositionOf(startSub, mpf, 3, 5, 7)
|
||||
val declFoo = startSub.statements.filterIsInstance<VarDecl>()[0]
|
||||
assertPositionOf(declFoo, mpf, 4, 8, 12)
|
||||
assertPositionOf(declFoo, mpf, 4, 9, 13)
|
||||
val rhsFoo = declFoo.value!!
|
||||
assertPositionOf(rhsFoo, mpf, 4, 20, 21)
|
||||
assertPositionOf(rhsFoo, mpf, 4, 21, 22)
|
||||
val declBar = startSub.statements.filterIsInstance<VarDecl>()[1]
|
||||
assertPositionOf(declBar, mpf, 5, 8, 12)
|
||||
assertPositionOf(declBar, mpf, 5, 9, 13)
|
||||
val whenStmt = startSub.statements.filterIsInstance<WhenStatement>()[0]
|
||||
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)
|
||||
assertPositionOf(whenStmt, mpf, 6, 9, 12)
|
||||
assertPositionOf(whenStmt.choices[0], mpf, 7, 13, 14)
|
||||
assertPositionOf(whenStmt.choices[1], mpf, 8, 13, 14)
|
||||
assertPositionOf(whenStmt.choices[2], mpf, 9, 13, 16)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +357,7 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
|
||||
test("isn't absolute for filesystem paths") {
|
||||
val path = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertSomethingForAllNodes(module) {
|
||||
Path(it.position.file).isAbsolute shouldBe false
|
||||
@@ -769,4 +772,33 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("test string x and u escape sequences") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
str string = "\x00\xff\u0041"
|
||||
ubyte zero = '\x00'
|
||||
ubyte ff = '\xff'
|
||||
ubyte letter = '\u0041'
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
val start = result.program.entrypoint
|
||||
val string = (start.statements[0] as VarDecl).value as StringLiteralValue
|
||||
withClue("x-escapes are hacked to range 0x8000-0x80ff") {
|
||||
string.value[0].code shouldBe 0x8000
|
||||
string.value[1].code shouldBe 0x80ff
|
||||
}
|
||||
string.value[2].code shouldBe 65
|
||||
val zero = start.statements[2] as Assignment
|
||||
zero.value shouldBe NumericLiteralValue(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
val ff = start.statements[4] as Assignment
|
||||
ff.value shouldBe NumericLiteralValue(DataType.UBYTE, 255.0, Position.DUMMY)
|
||||
val letter = start.statements[6] as Assignment
|
||||
// TODO characters should not be encoded until code generation, like strings...
|
||||
val encodedletter = Petscii.encodePetscii("A", true).getOrElse { fail("petscii error") }.single()
|
||||
letter.value shouldBe NumericLiteralValue(DataType.UBYTE, encodedletter.toDouble(), Position.DUMMY)
|
||||
}
|
||||
})
|
||||
@@ -12,9 +12,9 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.ast.helpers.DummyFunctions
|
||||
import prog8tests.ast.helpers.DummyMemsizer
|
||||
import prog8tests.ast.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
||||
class TestProgram: FunSpec({
|
||||
|
||||
@@ -6,10 +6,10 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.parser.SourceCode
|
||||
import prog8.parser.SourceCode.Companion.libraryFilePrefix
|
||||
import prog8tests.ast.helpers.assumeNotExists
|
||||
import prog8tests.ast.helpers.assumeReadableFile
|
||||
import prog8tests.ast.helpers.fixturesDir
|
||||
import prog8tests.ast.helpers.resourcesDir
|
||||
import prog8tests.helpers.assumeNotExists
|
||||
import prog8tests.helpers.assumeReadableFile
|
||||
import prog8tests.helpers.fixturesDir
|
||||
import prog8tests.helpers.resourcesDir
|
||||
import kotlin.io.path.Path
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
@Test
|
||||
fun testFromPathWithMissingExtension_p8() {
|
||||
val pathWithoutExt = assumeNotExists(fixturesDir,"simple_main")
|
||||
assumeReadableFile(fixturesDir,"simple_main.p8")
|
||||
assumeReadableFile(fixturesDir,"ast_simple_main.p8")
|
||||
shouldThrow<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingPath() {
|
||||
val filename = "simple_main.p8"
|
||||
val filename = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(fixturesDir, filename)
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
@@ -63,7 +63,7 @@ class TestSourceCode: AnnotationSpec() {
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingNonNormalizedPath() {
|
||||
val filename = "simple_main.p8"
|
||||
val filename = "ast_simple_main.p8"
|
||||
val path = Path(".", "test", "..", "test", "fixtures", filename)
|
||||
val srcFile = assumeReadableFile(path).toFile()
|
||||
val src = SourceCode.File(path)
|
||||
File renamed without changes.
+7
-7
@@ -1,4 +1,4 @@
|
||||
package prog8tests.asmgen
|
||||
package prog8tests.codegeneration
|
||||
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
@@ -11,14 +11,14 @@ import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.C64Zeropage
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.asmgen.helpers.DummyFunctions
|
||||
import prog8tests.asmgen.helpers.DummyMemsizer
|
||||
import prog8tests.asmgen.helpers.DummyStringEncoder
|
||||
import prog8tests.asmgen.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import java.nio.file.Path
|
||||
|
||||
class AsmGenSymbolsTests: StringSpec({
|
||||
@@ -75,7 +75,7 @@ class AsmGenSymbolsTests: StringSpec({
|
||||
fun createTestAsmGen(program: Program): AsmGen {
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
|
||||
val zp = C64MachineDefinition.C64Zeropage(options)
|
||||
val zp = C64Zeropage(options)
|
||||
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of(""))
|
||||
return asmgen
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -34,3 +34,11 @@ internal val DummyStringEncoder = object : IStringEncoding {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal val AsciiStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> = str.map { it.code.toUByte() }
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
return bytes.joinToString()
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,9 @@ import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.C64Zeropage
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compilerinterface.*
|
||||
import prog8tests.ast.helpers.assumeReadableFile
|
||||
import prog8tests.ast.helpers.outputDir
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.name
|
||||
|
||||
@@ -39,7 +37,7 @@ internal fun compileFile(
|
||||
optimize: Boolean,
|
||||
fileDir: Path,
|
||||
fileName: String,
|
||||
outputDir: Path = prog8tests.ast.helpers.outputDir,
|
||||
outputDir: Path = prog8tests.helpers.outputDir,
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true,
|
||||
optFloatExpr: Boolean = true
|
||||
@@ -85,7 +83,7 @@ internal fun generateAssembly(
|
||||
options: CompilationOptions? = null
|
||||
): IAssemblyProgram {
|
||||
val coptions = options ?: CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, true, C64Target)
|
||||
val zp = C64MachineDefinition.C64Zeropage(coptions)
|
||||
val zp = C64Zeropage(coptions)
|
||||
coptions.compTarget.machine.zeropage=zp
|
||||
val asmgen = AsmGen(program, ErrorReporterForTests(), zp, coptions, C64Target, outputDir)
|
||||
return asmgen.compileToAssembly()
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package prog8tests.ast.helpers
|
||||
package prog8tests.helpers
|
||||
|
||||
fun <T, U> cartesianProduct(c1: Collection<T>, c2: Collection<U>): Sequence<Pair<T, U>> {
|
||||
return c1.flatMap { lhsElem -> c2.map { rhsElem -> lhsElem to rhsElem } }.asSequence()
|
||||
@@ -1,4 +1,4 @@
|
||||
package prog8tests.ast.helpers
|
||||
package prog8tests.helpers
|
||||
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.matchers.shouldBe
|
||||
@@ -1,10 +1,10 @@
|
||||
package prog8tests.ast
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8tests.ast.helpers.*
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.div
|
||||
|
||||
@@ -28,7 +28,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir / "simple_main.p8")
|
||||
assumeNotExists(fixturesDir / "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class PathsHelpersTests: FunSpec({
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
val path = fixturesDir / "simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists("$path")
|
||||
}
|
||||
@@ -73,7 +73,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<java.lang.AssertionError> {
|
||||
assumeNotExists(fixturesDir, "simple_main.p8")
|
||||
assumeNotExists(fixturesDir, "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class PathsHelpersTests: FunSpec({
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
val path = fixturesDir / "simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory(path)
|
||||
}
|
||||
@@ -119,7 +119,7 @@ class PathsHelpersTests: FunSpec({
|
||||
}
|
||||
|
||||
test("on existing file") {
|
||||
val path = fixturesDir / "simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("$path")
|
||||
}
|
||||
@@ -142,7 +142,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory(fixturesDir, "simple_main.p8")
|
||||
assumeDirectory(fixturesDir, "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", "simple_main.p8")
|
||||
assumeDirectory("$fixturesDir", "ast_simple_main.p8")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on existing file") {
|
||||
shouldThrow<AssertionError> {
|
||||
assumeDirectory("$fixturesDir", Path("simple_main.p8"))
|
||||
assumeDirectory("$fixturesDir", Path("ast_simple_main.p8"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ class PathsHelpersTests: FunSpec({
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
val path = fixturesDir / "simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
withClue("should return the path") {
|
||||
assumeReadableFile(path) shouldBe path
|
||||
}
|
||||
@@ -232,7 +232,7 @@ class PathsHelpersTests: FunSpec({
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
val path = fixturesDir / "simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
withClue("should return the resulting path") {
|
||||
assumeReadableFile("$path") shouldBe path
|
||||
}
|
||||
@@ -253,9 +253,9 @@ class PathsHelpersTests: FunSpec({
|
||||
}
|
||||
|
||||
test("on readable file") {
|
||||
val path = fixturesDir / "simple_main.p8"
|
||||
val path = fixturesDir / "ast_simple_main.p8"
|
||||
withClue("should return the resulting path") {
|
||||
assumeReadableFile(fixturesDir / "simple_main.p8") shouldBe path
|
||||
assumeReadableFile(fixturesDir / "ast_simple_main.p8") shouldBe path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on readable file") {
|
||||
withClue("should return the resulting path") {
|
||||
assumeReadableFile(fixturesDir / Path("simple_main.p8")) shouldBe fixturesDir / "simple_main.p8"
|
||||
assumeReadableFile(fixturesDir / Path("ast_simple_main.p8")) shouldBe fixturesDir / "ast_simple_main.p8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ class PathsHelpersTests: FunSpec({
|
||||
|
||||
test("on readable file") {
|
||||
withClue("should return the resulting path") {
|
||||
assumeReadableFile(fixturesDir / "simple_main.p8") shouldBe fixturesDir / "simple_main.p8"
|
||||
assumeReadableFile(fixturesDir / "ast_simple_main.p8") shouldBe fixturesDir / "ast_simple_main.p8"
|
||||
}
|
||||
}
|
||||
|
||||
+13
-23
@@ -1,7 +1,6 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id "io.kotest" version "0.3.8"
|
||||
}
|
||||
|
||||
java {
|
||||
@@ -10,12 +9,23 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation 'org.antlr:antlr4-runtime:4.9.2'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
implementation project(':parser')
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
@@ -28,24 +38,4 @@ sourceSets {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,13 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
|
||||
<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="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="parser" />
|
||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -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.
|
||||
@@ -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, start.charPositionInLine + start.stopIndex - start.startIndex)
|
||||
return Position(filename, start.line, start.charPositionInLine+1, start.charPositionInLine + 1 + start.stopIndex - start.startIndex)
|
||||
}
|
||||
|
||||
internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
|
||||
@@ -288,8 +288,10 @@ private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCall {
|
||||
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.InlineasmContext.toAst() =
|
||||
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
||||
private fun Prog8ANTLRParser.InlineasmContext.toAst(): InlineAssembly {
|
||||
val text = INLINEASMBLOCK().text
|
||||
return InlineAssembly(text.substring(2, text.length-2), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {
|
||||
return Return(expression()?.toAst(), toPosition())
|
||||
@@ -356,8 +358,7 @@ private fun Prog8ANTLRParser.DirectiveargContext.toAst() : DirectiveArg {
|
||||
val str = stringliteral()
|
||||
if(str?.ALT_STRING_ENCODING() != null)
|
||||
throw SyntaxError("can't use alternate string s for directive arguments", toPosition())
|
||||
|
||||
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
|
||||
return DirectiveArg(str?.text?.substring(1, text.length-1), identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
@@ -480,11 +481,15 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
|
||||
throw FatalAstException(text)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.CharliteralContext.toAst(): CharLiteral =
|
||||
CharLiteral(unescape(this.SINGLECHAR().text, toPosition())[0], this.ALT_STRING_ENCODING() != null, toPosition())
|
||||
private fun Prog8ANTLRParser.CharliteralContext.toAst(): CharLiteral {
|
||||
val text = this.SINGLECHAR().text
|
||||
return CharLiteral(unescape(text.substring(1, text.length-1), toPosition())[0], this.ALT_STRING_ENCODING() != null, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteralValue =
|
||||
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
|
||||
private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteralValue {
|
||||
val text=this.STRING().text
|
||||
return StringLiteralValue(unescape(text.substring(1, text.length-1), toPosition()), ALT_STRING_ENCODING() != null, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
|
||||
return ArrayIndexedExpression(scoped_identifier().toAst(),
|
||||
@@ -540,7 +545,7 @@ private fun Prog8ANTLRParser.BranchconditionContext.toAst() = BranchCondition.va
|
||||
)
|
||||
|
||||
private fun Prog8ANTLRParser.ForloopContext.toAst(): ForLoop {
|
||||
val loopvar = identifier().toAst()
|
||||
val loopvar = scoped_identifier().toAst()
|
||||
val iterable = expression()!!.toAst()
|
||||
val scope =
|
||||
if(statement()!=null)
|
||||
|
||||
@@ -2,6 +2,7 @@ package prog8.ast.antlr
|
||||
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.SyntaxError
|
||||
import kotlin.NumberFormatException
|
||||
|
||||
fun escape(str: String): String {
|
||||
val es = str.map {
|
||||
@@ -32,12 +33,24 @@ fun unescape(str: String, position: Position): String {
|
||||
'"' -> '"'
|
||||
'\'' -> '\''
|
||||
'u' -> {
|
||||
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
||||
try {
|
||||
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
||||
} catch (sb: StringIndexOutOfBoundsException) {
|
||||
throw SyntaxError("invalid \\u escape sequence", position)
|
||||
} catch (nf: NumberFormatException) {
|
||||
throw SyntaxError("invalid \\u escape sequence", position)
|
||||
}
|
||||
}
|
||||
'x' -> {
|
||||
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
|
||||
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
|
||||
(0x8000 + hex).toChar()
|
||||
try {
|
||||
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
|
||||
(0x8000 + hex).toChar()
|
||||
} catch (sb: StringIndexOutOfBoundsException) {
|
||||
throw SyntaxError("invalid \\x escape sequence", position)
|
||||
} catch (nf: NumberFormatException) {
|
||||
throw SyntaxError("invalid \\x escape sequence", position)
|
||||
}
|
||||
}
|
||||
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package prog8.ast.base
|
||||
|
||||
import prog8.ast.Node
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolute
|
||||
|
||||
|
||||
/**************************** AST Data classes ****************************/
|
||||
@@ -35,9 +37,6 @@ enum class DataType {
|
||||
}
|
||||
|
||||
fun oneOf(vararg types: DataType) = this in types
|
||||
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
|
||||
infix fun isNotAssignableTo(targetType: DataType) = !this.isAssignableTo(targetType)
|
||||
infix fun isNotAssignableTo(targetTypes: Set<DataType>) = !this.isAssignableTo(targetTypes)
|
||||
|
||||
infix fun largerThan(other: DataType) =
|
||||
when {
|
||||
@@ -190,7 +189,7 @@ object ParentSentinel : Node {
|
||||
|
||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
||||
fun toClickableStr(): String = "$file:$line:$startCol:"
|
||||
fun toClickableStr(): String = "(${Path("").absolute()}/$file:$line:$startCol)"
|
||||
|
||||
companion object {
|
||||
val DUMMY = Position("<dummy>", 0, 0, 0)
|
||||
|
||||
@@ -202,11 +202,7 @@ open class VarDecl(val type: VarDeclType,
|
||||
|
||||
fun createAuto(array: ArrayLiteralValue): VarDecl {
|
||||
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||
val arrayDt =
|
||||
if(!array.type.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
else
|
||||
array.type.getOr(DataType.UNDEFINED)
|
||||
val arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") }
|
||||
val declaredType = ArrayToElementTypes.getValue(arrayDt)
|
||||
val arraysize = ArrayIndex.forArray(array)
|
||||
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array,
|
||||
@@ -710,7 +706,7 @@ class Subroutine(override val name: String,
|
||||
.asSequence()
|
||||
.filter { it is InlineAssembly }
|
||||
.map { (it as InlineAssembly).assembly }
|
||||
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it || " bra" in it || "\tbra" in it}
|
||||
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it || " bra" in it || "\tbra" in it }
|
||||
|
||||
|
||||
// code to provide the ability to reference asmsub parameters via qualified name:
|
||||
|
||||
@@ -6,7 +6,6 @@ import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.channels.Channels
|
||||
import java.nio.charset.CodingErrorAction
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
@@ -73,7 +72,7 @@ sealed class SourceCode {
|
||||
override val isFromResources = false
|
||||
override val isFromFilesystem = false
|
||||
override val origin = "$stringSourcePrefix${System.identityHashCode(text).toString(16)}>"
|
||||
override fun getCharStream(): CharStream = CharStreams.fromString(text, origin)
|
||||
public override fun getCharStream(): CharStream = CharStreams.fromString(text, origin)
|
||||
override fun readText() = text
|
||||
}
|
||||
|
||||
@@ -132,7 +131,7 @@ sealed class SourceCode {
|
||||
val inpStr = object {}.javaClass.getResourceAsStream(normalized)!!
|
||||
// CharStreams.fromStream() doesn't allow us to set the stream name properly, so we use a lower level api
|
||||
val channel = Channels.newChannel(inpStr)
|
||||
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1)
|
||||
return CharStreams.fromChannel(channel, Charsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1)
|
||||
}
|
||||
|
||||
override fun readText(): String {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import kotlin.math.max
|
||||
|
||||
object ProjectConfig : AbstractProjectConfig() {
|
||||
override val parallelism = max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
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) = 0
|
||||
}
|
||||
|
||||
internal val DummyStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal val AsciiStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> = str.map { it.code.toUByte() }
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
return bytes.joinToString()
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,18 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':compilerAst')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<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="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
|
||||
@@ -189,9 +189,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
}
|
||||
if(arglist is IdentifierReference) {
|
||||
val idt = arglist.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("couldn't determine type of iterable $arglist")
|
||||
return when(val dt = idt.getOr(DataType.UNDEFINED)) {
|
||||
return when(val dt = idt.getOrElse { throw FatalAstException("unknown dt") }) {
|
||||
DataType.STR, in NumericDatatypes -> dt
|
||||
in ArrayDatatypes -> ArrayToElementTypes.getValue(dt)
|
||||
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.compilerinterface
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
@@ -33,4 +34,5 @@ interface IMachineDefinition {
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
fun isIOAddress(address: UInt): Boolean
|
||||
fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>>
|
||||
}
|
||||
@@ -12,6 +12,18 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.9" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.9 (py3)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -294,6 +294,11 @@ This way you can set the second character on the second row from the top like th
|
||||
|
||||
top5screenrows[41] = '!'
|
||||
|
||||
**Array indexing on a pointer variable:**
|
||||
An uword variable can be used in limited scenarios as a 'pointer' to a byte in memory at a specific,
|
||||
dynamic, location. You can use array indexing on a pointer variable to use it as a byte array at
|
||||
a dynamic location in memory: currently this is equivalent to directly referencing the bytes in
|
||||
memory at the given index. See also :ref:`pointervars_programming`
|
||||
|
||||
Strings
|
||||
^^^^^^^
|
||||
@@ -368,6 +373,7 @@ address you specified, and setting the varible will directly modify that memory
|
||||
const byte max_age = 2000 - 1974 ; max_age will be the constant value 26
|
||||
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
|
||||
|
||||
.. _pointervars_programming:
|
||||
|
||||
Direct access to memory locations
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -427,7 +433,7 @@ Loops
|
||||
-----
|
||||
|
||||
The *for*-loop is used to let a variable iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
||||
The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions.
|
||||
The loop variable must be declared separately as byte or word earlier, so that you can reuse it for multiple occasions.
|
||||
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
|
||||
|
||||
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
|
||||
|
||||
@@ -361,6 +361,8 @@ should be the *memory address* where the value is located::
|
||||
&ubyte[5*40] top5screenrows = $0400 ; works for array as well
|
||||
|
||||
|
||||
.. _pointervars:
|
||||
|
||||
Direct access to memory locations
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Instead of defining a memory mapped name for a specific memory location, you can also
|
||||
@@ -370,7 +372,7 @@ directly access the memory. Enclose a numeric expression or literal with ``@(...
|
||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
|
||||
|
||||
The array indexing notation is syntactic sugar for such a direct memory access expression::
|
||||
The array indexing notation on a uword 'pointer variable' is syntactic sugar for such a direct memory access expression::
|
||||
|
||||
pointervar[999] = 0 ; equivalent to @(pointervar+999) = 0
|
||||
|
||||
@@ -420,6 +422,9 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
|
||||
array[2] ; the third byte in the array (index is 0-based)
|
||||
string[4] ; the fifth character (=byte) in the string
|
||||
|
||||
Note: you can also use array indexing on a 'pointer variable', which is basically an uword variable
|
||||
containing a memory address. Currently this is equivalent to directly referencing the bytes in
|
||||
memory at the given index. See :ref:`pointervars`
|
||||
|
||||
String
|
||||
^^^^^^
|
||||
@@ -638,8 +643,7 @@ Loops
|
||||
for loop
|
||||
^^^^^^^^
|
||||
|
||||
The loop variable must be a byte or word variable,
|
||||
and must be defined first in the local scope of the for loop.
|
||||
The loop variable must be a byte or word variable, and it must be defined separately first.
|
||||
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
|
||||
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
For next compiler release (7.5)
|
||||
For next compiler release (7.6)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
|
||||
@@ -19,10 +19,9 @@ Future
|
||||
- fix the asm-labels problem (github issue #62)
|
||||
- get rid of all TODO's in the code
|
||||
- improve testability further, add more tests
|
||||
- replace certain uses of inferredType.getOr(DataType.UNDEFINED) by i.getOrElse({ errorhandler })
|
||||
- see if we can remove more "[InferredType].getOr(DataType.UNDEFINED)"
|
||||
- use more of Result<> and Either<> to handle errors/ nulls better
|
||||
- can we get rid of pieces of asmgen.AssignmentAsmGen by just reusing the AugmentableAssignment ? generated code should not suffer
|
||||
- add a switch to not create the globals-initialization logic, but instead create a smaller program (that can only run once though)
|
||||
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
|
||||
- optimize several inner loops in gfx2 even further?
|
||||
- add modes 2 and 3 to gfx2 (lowres 4 color and 16 color)?
|
||||
@@ -42,6 +41,8 @@ More code optimization ideas
|
||||
by rewriting while and until expressions into if+jump (just consider them syntactic sugar)
|
||||
but the result should not produce larger code ofcourse!
|
||||
- while-expression should now also get the simplifyConditionalExpression() treatment
|
||||
- byte typed expressions should be evaluated in the accumulator where possible, without (temp)var
|
||||
for instance value = otherbyte >> 1 --> lda otherbite ; lsr a; sta value
|
||||
- rewrite expression tree evaluation such that it doesn't use an eval stack but flatten the tree into linear code that uses a fixed number of predetermined value 'variables'
|
||||
- this removes the need for the BinExprSplitter (which is problematic now)
|
||||
- this removes the need for the BinExprSplitter? (which is problematic and very limited now)
|
||||
- introduce byte-index operator to avoid index multiplications in loops over arrays? see github issue #4
|
||||
+4
-29
@@ -1,36 +1,11 @@
|
||||
%import textio
|
||||
%import test_stack
|
||||
%zeropage dontuse
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
ubyte @shared dummy
|
||||
word b1 = 1111
|
||||
byte b2 = 22
|
||||
word b3 = 3333
|
||||
dummy++
|
||||
func(-b1,-b2,-b3)
|
||||
}
|
||||
|
||||
sub printz(word a1, byte a2, word a3) {
|
||||
txt.print_w(a1)
|
||||
txt.spc()
|
||||
txt.print_b(a2)
|
||||
txt.spc()
|
||||
txt.print_w(a3)
|
||||
txt.nl()
|
||||
}
|
||||
asmsub func(word a1 @XY, byte a2 @A, word a3 @R0) {
|
||||
%asm {{
|
||||
stx printz.a1
|
||||
sty printz.a1+1
|
||||
sta printz.a2
|
||||
lda cx16.r0
|
||||
sta printz.a3
|
||||
lda cx16.r0+1
|
||||
sta printz.a3+1
|
||||
jmp printz
|
||||
}}
|
||||
ubyte @shared xx = @(cx16.r5)
|
||||
xx++
|
||||
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,18 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':compilerInterfaces')
|
||||
implementation project(':compiler')
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compiler" />
|
||||
|
||||
@@ -33,31 +33,16 @@ ALT_STRING_ENCODING: '@';
|
||||
FLOAT_NUMBER : FNUMBER (('E'|'e') ('+' | '-')? FNUMBER)? ; // sign comes later from unary expression
|
||||
fragment FNUMBER : ('0' .. '9') + ('.' ('0' .. '9') +)? ;
|
||||
|
||||
fragment STRING_ESCAPE_SEQ : '\\' . | '\\' EOL;
|
||||
fragment STRING_ESCAPE_SEQ : '\\' . | '\\x' . . | '\\u' . . . .;
|
||||
STRING :
|
||||
'"' ( STRING_ESCAPE_SEQ | ~[\\\r\n\f"] )* '"'
|
||||
{
|
||||
// get rid of the enclosing quotes
|
||||
String s = getText();
|
||||
setText(s.substring(1, s.length() - 1));
|
||||
}
|
||||
;
|
||||
INLINEASMBLOCK :
|
||||
'{{' .+? '}}'
|
||||
{
|
||||
// get rid of the enclosing double braces
|
||||
String s = getText();
|
||||
setText(s.substring(2, s.length() - 2));
|
||||
}
|
||||
;
|
||||
|
||||
SINGLECHAR :
|
||||
'\'' ( STRING_ESCAPE_SEQ | ~[\\\r\n\f"] ) '\''
|
||||
{
|
||||
// get rid of the enclosing quotes
|
||||
String s = getText();
|
||||
setText(s.substring(1, s.length() - 1));
|
||||
}
|
||||
'\'' ( STRING_ESCAPE_SEQ | ~[\\\r\n\f'] ) '\''
|
||||
;
|
||||
|
||||
ZEROPAGE :
|
||||
@@ -298,7 +283,7 @@ branch_stmt : branchcondition EOL? (statement | statement_block) EOL? else_part?
|
||||
branchcondition: 'if_cs' | 'if_cc' | 'if_eq' | 'if_z' | 'if_ne' | 'if_nz' | 'if_pl' | 'if_pos' | 'if_mi' | 'if_neg' | 'if_vs' | 'if_vc' ;
|
||||
|
||||
|
||||
forloop : 'for' identifier 'in' expression EOL? (statement | statement_block) ;
|
||||
forloop : 'for' scoped_identifier 'in' expression EOL? (statement | statement_block) ;
|
||||
|
||||
whileloop: 'while' expression EOL? (statement | statement_block) ;
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||
</component>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Prog8 syntax highlighting file for IntelliJ IDEA.
|
||||
|
||||
Copy the file Prog8.xml to your IDEA filetypes folder.
|
||||
If this folder doesn't yet exist, simply create it.
|
||||
After installing this file, restart the IDEA.
|
||||
|
||||
The exact path may vary with the version of the IDE,
|
||||
but for me it is currently this on Linux:
|
||||
The exact location of the folder varies per operating system and
|
||||
IDE version, but for me it is currently this on Linux:
|
||||
|
||||
$HOME/.config/JetBrains/IntelliJIdea2020.2/filetypes/
|
||||
$HOME/.config/JetBrains/IntelliJIdea2021.3/filetypes/
|
||||
|
||||
|
||||
(note the version number in the path, adjust accordingly)
|
||||
Reference in new issue
Block a user