Compare commits

..
36 Commits
Author SHA1 Message Date
Irmen de Jong 35998142fe version 7.5 2021-12-10 20:18:17 +01:00
Irmen de Jong 75ea453bf4 fix asm code optimization problem caused in previous release where asm file is not read back in separate lines anymore 2021-12-10 15:37:53 +01:00
Irmen de Jong 33061aaa0d fix: allow scoped variables such as cx16.rX as loop variable in forloops 2021-12-10 14:59:04 +01:00
Irmen de Jong e342311bef fix wrong code for inplace modification of a pointervariable's memory value 2021-12-10 14:48:53 +01:00
Irmen de Jong 3d743a1ba1 added more constfolding 2021-12-09 23:32:48 +01:00
Irmen de Jong abca618008 added more constfolding 2021-12-09 23:12:12 +01:00
Irmen de Jong 0d2c3901a3 added more constfolding 2021-12-09 22:12:31 +01:00
Irmen de Jong d901a1531f added missing vectors to syslib 2021-12-09 21:38:00 +01:00
Irmen de Jong d8d56b195f comments 2021-12-09 21:13:13 +01:00
Irmen de Jong a52699717c Merge remote-tracking branch 'origin/master'
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	compiler/test/TestSubroutines.kt
#	examples/test.p8
2021-12-07 23:29:30 +01:00
Irmen de Jong 98315de723 allow using ubyte[] as subroutine parameter type (because it is equivalent to uword pointer var) 2021-12-07 23:28:45 +01:00
Irmen de Jong 68d2f7d4c0 allow using ubyte[] as subroutine parameter type (because it is equivalent to uword pointer var) 2021-12-07 23:21:49 +01:00
Irmen de Jong c812b5ee09 elaborate pointervar indexing a bit more in the docs 2021-12-07 22:25:14 +01:00
Irmen de Jong dcf487bdc1 fix: correctly insert return statement if needed to prevent 'fall through' into following subroutine
this wasn't working correctly anymore when the last statement before the subroutine was a jump/goto
2021-12-07 21:34:50 +01:00
Irmen de Jong 547b1d3720 comment corrections 2021-12-06 23:33:18 +01:00
Irmen de Jong 84f75f4156 tweaked some more .getOrElse 2021-12-06 21:22:00 +01:00
Irmen de Jong ff69da3fa2 error when 'else' choice in when statemetn isn't the last one, also generate slightly better code for when statements 2021-12-05 21:54:46 +01:00
Irmen de Jong edffe92a24 astchecker is smarter in detecting rts in inline assembly 2021-12-05 21:28:31 +01:00
Irmen de Jong b6fe40ada4 fix: cx16.r0 now properly treated as zeropage var on cx16 so @(cx16.r0) won't copy it to temp var anymore 2021-12-05 21:21:41 +01:00
Irmen de Jong 837804b231 test for string x and u escape sequences 2021-12-05 18:39:34 +01:00
Irmen de Jong 81deed143b fix grammar problem: \x and \u escape sequences didn't work in character literals. 2021-12-05 18:11:40 +01:00
Irmen de Jong 900cdd3fa1 added cx16diskio with load() and load_raw() that are HIMEM bank-aware 2021-12-05 02:20:48 +01:00
Irmen de Jong 0018dc6ce7 refactor machinedefinition 2021-12-04 19:07:19 +01:00
Irmen de Jong c92f914081 gradle build settings tweak to avoid jdk version conflict 2021-12-04 18:36:47 +01:00
Irmen de Jong 0498444ef2 moved all unit tests into single project to avoid dependency issues 2021-12-04 18:20:22 +01:00
Irmen de Jong ce3c34e458 tweak in error output for file links, corrected column number off-by-one 2021-12-04 16:52:03 +01:00
Irmen de Jong 20401b99d8 added cx16.getrambank() / getrombank() to retrieve the current ram and rom bank numbers. 2021-12-04 15:27:54 +01:00
Irmen de Jong 397f98513b optimize loading A from pointervar 2021-12-04 05:36:48 +01:00
Irmen de Jong e545ea9504 fix and optimize storing A into pointervar 2021-12-04 04:43:58 +01:00
Irmen de Jong b867d8f731 cleanups 2021-12-04 01:03:51 +01:00
Irmen de Jong 9a68864b67 version 7.5-dev 2021-12-04 00:18:44 +01:00
Irmen de Jong 72d7178762 added diskio.load_raw() to load headerless files 2021-12-04 00:07:21 +01:00
Irmen de Jong fbcd9a0c1d reduce number of similar errors for type problem in assignment 2021-12-02 17:44:52 +01:00
Irmen de Jong c3144a20db spacing 2021-12-02 00:10:06 +01:00
Irmen de Jong 5b56e0462d also deal with zero args 2021-12-01 22:26:36 +01:00
Irmen de Jong b7fffbb6df release 7.4.1 - oops, funcion call arg count validation was broken 2021-12-01 21:44:03 +01:00
100 changed files with 1199 additions and 879 deletions

No files matched your search

+10
View File
@@ -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>
+5
View File
@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
+1
View File
@@ -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" />
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="11" />
</component>
</project>
-10
View File
@@ -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>
-17
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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!
+1 -4
View File
@@ -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>
+2
View File
@@ -0,0 +1,2 @@
Unittests for things in this module are located in the Compiler module instead,
for convenience sake, and to not spread the test cases around too much.
@@ -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")
@@ -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)
}
}
}
@@ -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))
@@ -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()
}
}
-8
View File
@@ -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)
}
-37
View File
@@ -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()
}
}
+12
View File
@@ -11,6 +11,18 @@ java {
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':compilerInterfaces')
implementation project(':compilerAst')
+1 -2
View File
@@ -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
View File
@@ -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 {
+2 -1
View File
@@ -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>
+3 -1
View File
@@ -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
+38
View File
@@ -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
}
}
+18 -1
View File
@@ -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 {{
+34 -3
View File
@@ -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
View File
@@ -1 +1 @@
7.4
7.5
+4 -4
View File
@@ -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))
+3 -4
View File
@@ -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()
}
+1 -1
View File
@@ -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
+14 -14
View File
@@ -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>()
+1 -1
View File
@@ -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
+3 -4
View File
@@ -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") {
+4 -7
View File
@@ -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({
+3 -5
View File
@@ -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)
+115 -1
View File
@@ -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
+116 -39
View File
@@ -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>()
}
})
+2 -2
View File
@@ -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.
@@ -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.
+8
View File
@@ -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()
}
}
+3 -5
View File
@@ -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,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
View File
@@ -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"
}
}
+1 -4
View File
@@ -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>
+2
View File
@@ -0,0 +1,2 @@
Unittests for things in this module are located in the Compiler module instead,
for convenience sake, and to not spread the test cases around too much.
+15 -10
View File
@@ -29,7 +29,7 @@ private fun ParserRuleContext.toPosition() : Position {
pathString
}
// note: beware of TAB characters in the source text, they count as 1 column...
return Position(filename, start.line, start.charPositionInLine, 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)
+16 -3
View File
@@ -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)
})
+3 -4
View File
@@ -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:
+2 -3
View File
@@ -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 {
-8
View File
@@ -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)
}
-44
View File
@@ -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()
}
}
+12
View File
@@ -11,6 +11,18 @@ java {
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+1 -1
View File
@@ -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
View File
@@ -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"
+1 -1
View File
@@ -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
View File
@@ -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>
+7 -1
View File
@@ -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.
+7 -3
View File
@@ -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).
+5 -4
View File
@@ -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
View File
@@ -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
View File
@@ -12,6 +12,18 @@ java {
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':compilerInterfaces')
implementation project(':compiler')
+1 -1
View File
@@ -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" />
+3 -18
View File
@@ -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
View File
@@ -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>
+5 -5
View File
@@ -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)