mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53bf8c09fd | ||
|
|
651c383668 | ||
|
|
9ed7587e3e | ||
|
|
674295e800 | ||
|
|
6b02f2eea0 | ||
|
|
5237e55326 | ||
|
|
3b59592110 | ||
|
|
72640ae058 | ||
|
|
d916027e75 | ||
|
|
8966d2aa06 | ||
|
|
de7ea04f54 | ||
|
|
bf71fabe0e | ||
|
|
b3368acb33 | ||
|
|
87220c6697 | ||
|
|
a3b5c2ad71 | ||
|
|
fb4c1473c5 | ||
|
|
2bb2502d20 | ||
|
|
fe51698579 | ||
|
|
0f0f40bff3 | ||
|
|
a798fe72d3 | ||
|
|
fba98d03a5 | ||
|
|
7dd2517f67 | ||
|
|
641477d6f6 | ||
|
|
8e56656c8d | ||
|
|
564a6a1f62 | ||
|
|
69f0c80cd7 | ||
|
|
6fcb51cea2 | ||
|
|
c58b8a4973 | ||
|
|
c8f4ab4f06 | ||
|
|
e425c4cca8 | ||
|
|
056ec986c2 | ||
|
|
de3b2fb95b | ||
|
|
789e39c719 | ||
|
|
b29c3152db | ||
|
|
3831679772 | ||
|
|
596f9566d8 | ||
|
|
124befe9d6 | ||
|
|
895534f32b | ||
|
|
50c16fe6de | ||
|
|
b092d1a5d3 | ||
|
|
a9b45630d7 | ||
|
|
c1a39c269e | ||
|
|
6fa3f0b6cd | ||
|
|
9e5e3d1559 | ||
|
|
7135205299 | ||
|
|
d99d977d2b | ||
|
|
7dd7e562bc | ||
|
|
75d857027e | ||
|
|
17694c1d01 | ||
|
|
749ad700d8 | ||
|
|
8f3df3039a | ||
|
|
02c315c194 | ||
|
|
b697375573 |
No files matched your search
@@ -1,29 +1,22 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.c128.C128MachineDefinition
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
|
||||
object C128Target: ICompilationTarget {
|
||||
object C128Target: ICompilationTarget, IStringEncoding by Encoder {
|
||||
override val name = "c128"
|
||||
override val machine = C128MachineDefinition()
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.c64.C64MachineDefinition
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
|
||||
object C64Target: ICompilationTarget {
|
||||
object C64Target: ICompilationTarget, IStringEncoding by Encoder {
|
||||
override val name = "c64"
|
||||
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(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
@@ -8,25 +7,16 @@ import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.codegen.target.cx16.CX16MachineDefinition
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
|
||||
object Cx16Target: ICompilationTarget {
|
||||
object Cx16Target: ICompilationTarget, IStringEncoding by Encoder {
|
||||
override val name = "cx16"
|
||||
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)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.codegen.target.cbm.IsoEncoding
|
||||
import prog8.codegen.target.cbm.PetsciiEncoding
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
internal object Encoder: IStringEncoding {
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> { // TODO use Result
|
||||
val coded = when(encoding) {
|
||||
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
|
||||
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
|
||||
Encoding.ISO -> IsoEncoding.encode(str)
|
||||
else -> throw FatalAstException("unsupported encoding $encoding")
|
||||
}
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String { // TODO use Result
|
||||
val decoded = when(encoding) {
|
||||
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
|
||||
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
|
||||
Encoding.ISO -> IsoEncoding.decode(bytes)
|
||||
else -> throw FatalAstException("unsupported encoding $encoding")
|
||||
}
|
||||
return decoded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,6 @@ class C128MachineDefinition: 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 = C128Zeropage(compilerOptions)
|
||||
|
||||
@@ -57,7 +57,6 @@ class 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)
|
||||
|
||||
@@ -3,10 +3,10 @@ package prog8.codegen.target.cbm
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.mapError
|
||||
import prog8.codegen.target.cpu6502.codegen.generatedLabelPrefix
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IAssemblyProgram
|
||||
import prog8.compilerinterface.OutputType
|
||||
import prog8.compilerinterface.generatedLabelPrefix
|
||||
import prog8.parser.SourceCode
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package prog8.codegen.target.cbm
|
||||
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Err
|
||||
import java.io.CharConversionException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
object IsoEncoding {
|
||||
val charset: Charset = Charset.forName("ISO-8859-15")
|
||||
|
||||
fun encode(str: String): Result<List<UByte>, CharConversionException> {
|
||||
return try {
|
||||
Ok(str.toByteArray(charset).map { it.toUByte() })
|
||||
} catch (ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
|
||||
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
|
||||
return try {
|
||||
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
|
||||
} catch (ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-16
@@ -6,10 +6,10 @@ import com.github.michaelbull.result.Result
|
||||
import prog8.ast.antlr.escape
|
||||
import java.io.CharConversionException
|
||||
|
||||
object Petscii {
|
||||
object PetsciiEncoding {
|
||||
|
||||
// decoding: from Petscii/Screencodes (0-255) to unicode
|
||||
// character tables used from https://github.com/dj51d/cbmcodecs
|
||||
// character tables used from https://github.com/irmen/cbmcodecs2
|
||||
|
||||
private val decodingPetsciiLowercase = charArrayOf(
|
||||
'\u0000', // 0x00 -> \u0000
|
||||
@@ -1095,13 +1095,17 @@ object Petscii {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||
return petscii.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingPetsciiLowercase.size)
|
||||
throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
|
||||
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
|
||||
}.joinToString("")
|
||||
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> {
|
||||
return try {
|
||||
Ok(petscii.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingPetsciiLowercase.size)
|
||||
throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
|
||||
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
|
||||
}.joinToString(""))
|
||||
} catch(ce: CharConversionException) {
|
||||
return Err(ce)
|
||||
}
|
||||
}
|
||||
|
||||
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
|
||||
@@ -1134,13 +1138,17 @@ object Petscii {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||
return screencode.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingScreencodeLowercase.size)
|
||||
throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
|
||||
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
|
||||
}.joinToString("")
|
||||
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> {
|
||||
return try {
|
||||
Ok(screencode.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingScreencodeLowercase.size)
|
||||
throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
|
||||
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
|
||||
}.joinToString(""))
|
||||
} catch(ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
|
||||
fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> {
|
||||
@@ -1,6 +1,7 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import com.github.michaelbull.result.onSuccess
|
||||
import prog8.ast.*
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
@@ -28,6 +29,11 @@ import kotlin.io.path.writeLines
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
const val generatedLabelPrefix = "prog8_label_"
|
||||
const val subroutineFloatEvalResultVar1 = "prog8_float_eval_result1"
|
||||
const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
|
||||
|
||||
|
||||
class AsmGen(private val program: Program,
|
||||
val errors: IErrorReporter,
|
||||
val zeropage: Zeropage,
|
||||
@@ -42,12 +48,11 @@ 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 = compTarget.machine.getPreallocatedZeropageVars().toMutableMap()
|
||||
private val breakpointLabels = mutableListOf<String>()
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
|
||||
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
|
||||
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
|
||||
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
|
||||
private val expressionsAsmGen = ExpressionsAsmGen(program, this, functioncallAsmGen)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this)
|
||||
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
@@ -61,43 +66,97 @@ class AsmGen(private val program: Program,
|
||||
|
||||
println("Generating assembly code... ")
|
||||
|
||||
val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value }
|
||||
require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"}
|
||||
|
||||
header()
|
||||
val allBlocks = program.allBlocks
|
||||
if(allBlocks.first().name != "main")
|
||||
throw AssemblyError("first block should be 'main'")
|
||||
|
||||
for(b in program.allBlocks) {
|
||||
block2asm(b)
|
||||
}
|
||||
allocateAllZeropageVariables()
|
||||
if(errors.noErrors()) {
|
||||
program.allBlocks.forEach { block2asm(it) }
|
||||
|
||||
for(removal in removals.toList()) {
|
||||
removal.second.remove(removal.first)
|
||||
removals.remove(removal)
|
||||
}
|
||||
for(removal in removals.toList()) {
|
||||
removal.second.remove(removal.first)
|
||||
removals.remove(removal)
|
||||
}
|
||||
|
||||
slaballocations()
|
||||
footer()
|
||||
slaballocations()
|
||||
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)
|
||||
val output = outputDir.resolve("${program.name}.asm")
|
||||
if(options.optimize) {
|
||||
val separateLines = assemblyLines.flatMapTo(mutableListOf()) { it.split('\n') }
|
||||
assemblyLines.clear()
|
||||
while(optimizeAssembly(separateLines, options.compTarget.machine, program)>0) {
|
||||
// optimize the assembly source code
|
||||
}
|
||||
output.writeLines(separateLines)
|
||||
} else {
|
||||
output.writeLines(assemblyLines)
|
||||
}
|
||||
}
|
||||
|
||||
outputDir.resolve("${program.name}.asm").writeLines(assemblyLines)
|
||||
|
||||
return if(errors.noErrors())
|
||||
AssemblyProgram(true, program.name, outputDir, compTarget.name)
|
||||
else {
|
||||
errors.report()
|
||||
AssemblyProgram(false, "<error>", outputDir, compTarget.name)
|
||||
}
|
||||
}
|
||||
|
||||
private val varsInZeropage = mutableSetOf<VarDecl>()
|
||||
|
||||
private fun allocateAllZeropageVariables() {
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
return
|
||||
val allVariables = this.callGraph.allIdentifiers.asSequence()
|
||||
.map { it.value }
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter { it.type==VarDeclType.VAR }
|
||||
.toSet()
|
||||
.map { it to it.scopedName }
|
||||
val varsRequiringZp = allVariables.filter { it.first.zeropage==ZeropageWish.REQUIRE_ZEROPAGE }
|
||||
val varsPreferringZp = allVariables
|
||||
.filter { it.first.zeropage==ZeropageWish.PREFER_ZEROPAGE }
|
||||
.sortedBy { options.compTarget.memorySize(it.first.datatype) } // allocate the smallest DT first
|
||||
|
||||
for ((vardecl, scopedname) in varsRequiringZp) {
|
||||
val arraySize: Int? = when(vardecl.datatype) {
|
||||
DataType.STR -> {
|
||||
(vardecl.value as StringLiteralValue).value.length
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
vardecl.arraysize!!.constIndex()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
val result = zeropage.allocate(scopedname, vardecl.datatype, arraySize, vardecl.position, errors)
|
||||
result.fold(
|
||||
success = { varsInZeropage.add(vardecl) },
|
||||
failure = { errors.err(it.message!!, vardecl.position) }
|
||||
)
|
||||
}
|
||||
if(errors.noErrors()) {
|
||||
varsPreferringZp.forEach { (vardecl, scopedname) ->
|
||||
val arraySize: Int? = when (vardecl.datatype) {
|
||||
DataType.STR -> {
|
||||
(vardecl.value as StringLiteralValue).value.length
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
vardecl.arraysize!!.constIndex()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
val result = zeropage.allocate(scopedname, vardecl.datatype, arraySize, vardecl.position, errors)
|
||||
result.onSuccess { varsInZeropage.add(vardecl) }
|
||||
// no need to check for error, if there is one, just allocate in normal system ram later.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu
|
||||
internal fun haveFPWR() = compTarget is Cx16Target
|
||||
|
||||
@@ -142,9 +201,9 @@ class AsmGen(private val program: Program,
|
||||
out("* = ${program.actualLoadAddress.toHex()}")
|
||||
val year = LocalDate.now().year
|
||||
out(" .word (+), $year")
|
||||
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8'")
|
||||
out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
|
||||
out("+\t.word 0")
|
||||
out("_prog8_entrypoint\t; assembly code starts here\n")
|
||||
out("prog8_entrypoint\t; assembly code starts here\n")
|
||||
if(!options.noSysInit)
|
||||
out(" jsr ${compTarget.name}.init_system")
|
||||
out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
@@ -216,13 +275,9 @@ class AsmGen(private val program: Program,
|
||||
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
|
||||
|
||||
if(options.dontReinitGlobals) {
|
||||
block.statements.filterIsInstance<VarDecl>().forEach {
|
||||
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
|
||||
it.zeropage = ZeropageWish.NOT_IN_ZEROPAGE
|
||||
val init = it.nextSibling() as? Assignment
|
||||
if(init?.target?.identifier?.targetVarDecl(program) === it) {
|
||||
// put the init value back into the vardecl
|
||||
it.value = init.value
|
||||
}
|
||||
it.findInitializer(program)?.let { initializer -> it.value = initializer.value } // put the init value back into the vardecl
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +289,7 @@ class AsmGen(private val program: Program,
|
||||
memdefs2asm(block.statements, block)
|
||||
vardecls2asm(block.statements, block)
|
||||
|
||||
block.statements.filterIsInstance<VarDecl>().forEach {
|
||||
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
|
||||
if(it.type==VarDeclType.VAR && it.datatype in NumericDatatypes)
|
||||
it.value=null
|
||||
}
|
||||
@@ -283,35 +338,40 @@ class AsmGen(private val program: Program,
|
||||
|
||||
private fun zeropagevars2asm(statements: List<Statement>, inBlock: Block?) {
|
||||
out("; vars allocated on zeropage")
|
||||
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||
val variables = statements.asSequence().filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||
val blockname = inBlock?.name
|
||||
for(variable in variables) {
|
||||
if(blockname=="prog8_lib" && variable.name.startsWith("P8ZP_SCRATCH_"))
|
||||
continue // the "hooks" to the temp vars are not generated as new variables
|
||||
val fullName = variable.scopedName.joinToString(".")
|
||||
val zpVar = allocatedZeropageVariables[fullName]
|
||||
if(zpVar==null) {
|
||||
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
|
||||
val scopedName = variable.scopedName
|
||||
val zpAlloc = zeropage.allocatedZeropageVariable(scopedName)
|
||||
if (zpAlloc == null) {
|
||||
// This var is not on the ZP yet. Attempt to move it there if it's an integer type
|
||||
if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE &&
|
||||
variable.datatype in zeropage.allowedDatatypes
|
||||
&& variable.datatype != DataType.FLOAT
|
||||
&& options.zeropage != ZeropageType.DONTUSE) {
|
||||
try {
|
||||
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
|
||||
errors.report()
|
||||
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
|
||||
// make sure we add the var to the set of zpvars for this block
|
||||
allocatedZeropageVariables[fullName] = address to variable.datatype
|
||||
} catch (x: ZeropageDepletedError) {
|
||||
// leave it as it is.
|
||||
}
|
||||
variable.datatype in IntegerDatatypes
|
||||
&& options.zeropage != ZeropageType.DONTUSE) {
|
||||
val result = zeropage.allocate(scopedName, variable.datatype, null, null, errors)
|
||||
errors.report()
|
||||
result.fold(
|
||||
success = { (address, _) -> out("${variable.name} = $address\t; zp ${variable.datatype}") },
|
||||
failure = { /* leave it as it is, not on zeropage. */ }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Var has been placed in ZP, just output the address
|
||||
val lenspec = when(zpAlloc.second.first) {
|
||||
DataType.FLOAT,
|
||||
DataType.STR,
|
||||
in ArrayDatatypes -> " ${zpAlloc.second.second} bytes"
|
||||
else -> ""
|
||||
}
|
||||
out("${variable.name} = ${zpAlloc.first}\t; zp ${variable.datatype} $lenspec")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun vardecl2asm(decl: VarDecl) {
|
||||
val name = decl.name
|
||||
private fun vardecl2asm(decl: VarDecl, nameOverride: String?=null) {
|
||||
val name = nameOverride ?: decl.name
|
||||
val value = decl.value
|
||||
val staticValue: Number =
|
||||
if(value!=null) {
|
||||
@@ -341,9 +401,7 @@ class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
DataType.STR -> {
|
||||
throw AssemblyError("all string vars should have been interned")
|
||||
// val str = decl.value as StringLiteralValue
|
||||
// outputStringvar(decl, compTarget.encodeString(str.value, str.altEncoding).plus(0.toUByte()))
|
||||
throw AssemblyError("all string vars should have been interned into prog")
|
||||
}
|
||||
DataType.ARRAY_UB -> {
|
||||
val data = makeArrayFillDataUnsigned(decl)
|
||||
@@ -412,7 +470,7 @@ class AsmGen(private val program: Program,
|
||||
val blockname = inBlock?.name
|
||||
|
||||
out("\n; memdefs and kernal subroutines")
|
||||
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
|
||||
val memvars = statements.asSequence().filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
|
||||
for(m in memvars) {
|
||||
if(blockname!="prog8_lib" || !m.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables
|
||||
if(m.value is NumericLiteralValue)
|
||||
@@ -420,7 +478,7 @@ class AsmGen(private val program: Program,
|
||||
else
|
||||
out(" ${m.name} = ${asmVariableName((m.value as AddressOf).identifier)}")
|
||||
}
|
||||
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
|
||||
val asmSubs = statements.asSequence().filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
|
||||
for(sub in asmSubs) {
|
||||
val addr = sub.asmAddress
|
||||
if(addr!=null) {
|
||||
@@ -433,24 +491,20 @@ class AsmGen(private val program: Program,
|
||||
|
||||
private fun vardecls2asm(statements: List<Statement>, inBlock: Block?) {
|
||||
out("\n; non-zeropage variables")
|
||||
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||
val vars = statements.asSequence()
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter {
|
||||
it.type==VarDeclType.VAR && zeropage.allocatedZeropageVariable(it.scopedName)==null
|
||||
}
|
||||
|
||||
val encodedstringVars = vars
|
||||
.filter { it.datatype == DataType.STR && shouldActuallyOutputStringVar(it) }
|
||||
.map {
|
||||
val str = it.value as StringLiteralValue
|
||||
it to compTarget.encodeString(str.value, str.altEncoding).plus(0.toUByte())
|
||||
}
|
||||
for((decl, variables) in encodedstringVars) {
|
||||
outputStringvar(decl, variables)
|
||||
}
|
||||
vars.filter { it.datatype == DataType.STR && shouldActuallyOutputStringVar(it) }
|
||||
.forEach { outputStringvar(it) }
|
||||
|
||||
// non-string variables
|
||||
val blockname = inBlock?.name
|
||||
|
||||
vars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
|
||||
val scopedname = it.scopedName.joinToString(".")
|
||||
if(scopedname !in allocatedZeropageVariables) {
|
||||
if(!isZpVar(it.scopedName)) {
|
||||
if(blockname!="prog8_lib" || !it.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables
|
||||
vardecl2asm(it)
|
||||
}
|
||||
@@ -468,10 +522,11 @@ class AsmGen(private val program: Program,
|
||||
return !onlyInMemoryFuncs
|
||||
}
|
||||
|
||||
private fun outputStringvar(strdecl: VarDecl, bytes: List<UByte>) {
|
||||
private fun outputStringvar(strdecl: VarDecl, nameOverride: String?=null) {
|
||||
val varname = nameOverride ?: strdecl.name
|
||||
val sv = strdecl.value as StringLiteralValue
|
||||
val altEncoding = if(sv.altEncoding) "@" else ""
|
||||
out("${strdecl.name}\t; ${strdecl.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
|
||||
out("$varname\t; ${strdecl.datatype} ${sv.encoding}:\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
|
||||
val bytes = compTarget.encodeString(sv.value, sv.encoding).plus(0.toUByte())
|
||||
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
|
||||
for (chunk in outputBytes.chunked(16))
|
||||
out(" .byte " + chunk.joinToString())
|
||||
@@ -569,34 +624,16 @@ class AsmGen(private val program: Program,
|
||||
return identifier.nameInSource.joinToString(".")
|
||||
|
||||
val tgt2 = identifier.targetStatement(program)
|
||||
if (tgt2 == null && (identifier.nameInSource[0].startsWith("_prog8") || identifier.nameInSource[0].startsWith(
|
||||
"prog8"
|
||||
))
|
||||
)
|
||||
if (tgt2 == null && (identifier.nameInSource[0].startsWith("prog8")))
|
||||
return identifier.nameInSource.joinToString(".")
|
||||
|
||||
val target = identifier.targetStatement(program)!!
|
||||
val targetScope = target.definingSubroutine
|
||||
val identScope = identifier.definingSubroutine
|
||||
return if (targetScope !== identScope) {
|
||||
val scopedName = (target as INamedStatement).scopedName
|
||||
if (target is Label) {
|
||||
// make labels locally scoped in the asm. Is slightly problematic, see GitHub issue #62
|
||||
val newName = scopedName.dropLast(1) + ("_${scopedName.last()}")
|
||||
fixNameSymbols(newName.joinToString("."))
|
||||
}
|
||||
else {
|
||||
fixNameSymbols(scopedName.joinToString("."))
|
||||
}
|
||||
} else {
|
||||
if (target is Label) {
|
||||
// make labels locally scoped in the asm. Is slightly problematic, see GitHub issue #62
|
||||
val scopedName = identifier.nameInSource.toMutableList()
|
||||
val last = scopedName.removeLast()
|
||||
scopedName.add("_$last")
|
||||
fixNameSymbols(scopedName.joinToString("."))
|
||||
} else fixNameSymbols(identifier.nameInSource.joinToString("."))
|
||||
}
|
||||
return if (targetScope !== identScope)
|
||||
fixNameSymbols((target as INamedStatement).scopedName.joinToString("."))
|
||||
else
|
||||
fixNameSymbols(identifier.nameInSource.joinToString("."))
|
||||
}
|
||||
|
||||
return fixNameSymbols(internalName())
|
||||
@@ -616,6 +653,16 @@ class AsmGen(private val program: Program,
|
||||
fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
|
||||
fun getTempVarName(dt: DataType): List<String> {
|
||||
return when(dt) {
|
||||
DataType.UBYTE -> listOf("cx16", "r9L")
|
||||
DataType.BYTE -> listOf("cx16", "r9sL")
|
||||
DataType.UWORD -> listOf("cx16", "r9")
|
||||
DataType.WORD -> listOf("cx16", "r9s")
|
||||
DataType.FLOAT -> listOf("floats", "tempvar_swap_float") // defined in floats.p8
|
||||
else -> throw FatalAstException("invalid dt $dt")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): String {
|
||||
// returns the source name of the zero page pointervar if it's already in the ZP,
|
||||
@@ -628,9 +675,8 @@ class AsmGen(private val program: Program,
|
||||
}
|
||||
is VarDecl -> {
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val scopedName = target.scopedName.joinToString(".")
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
return if (isZpVar(scopedName)) {
|
||||
return if (isZpVar(target.scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" lda ($sourceName)")
|
||||
sourceName
|
||||
@@ -644,7 +690,7 @@ class AsmGen(private val program: Program,
|
||||
"P8ZP_SCRATCH_W1"
|
||||
}
|
||||
} else {
|
||||
return if (isZpVar(scopedName)) {
|
||||
return if (isZpVar(target.scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" ldy #0 | lda ($sourceName),y")
|
||||
sourceName
|
||||
@@ -667,9 +713,8 @@ 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)) {
|
||||
if (isZpVar(vardecl.scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" sta ($sourceName)")
|
||||
} else {
|
||||
@@ -681,7 +726,7 @@ class AsmGen(private val program: Program,
|
||||
sta (P8ZP_SCRATCH_W2)""")
|
||||
}
|
||||
} else {
|
||||
if (isZpVar(scopedName)) {
|
||||
if (isZpVar(vardecl.scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" ldy #0 | sta ($sourceName),y")
|
||||
} else {
|
||||
@@ -730,11 +775,11 @@ class AsmGen(private val program: Program,
|
||||
out(" pha")
|
||||
}
|
||||
CpuRegister.X -> {
|
||||
out(" stx _prog8_regsaveX")
|
||||
out(" stx prog8_regsaveX")
|
||||
scope.asmGenInfo.usedRegsaveX = true
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
out(" sty _prog8_regsaveY")
|
||||
out(" sty prog8_regsaveY")
|
||||
scope.asmGenInfo.usedRegsaveY = true
|
||||
}
|
||||
}
|
||||
@@ -777,8 +822,8 @@ class AsmGen(private val program: Program,
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pla") // this just used the stack but only for A
|
||||
CpuRegister.X -> out(" ldx _prog8_regsaveX")
|
||||
CpuRegister.Y -> out(" ldy _prog8_regsaveY")
|
||||
CpuRegister.X -> out(" ldx prog8_regsaveX")
|
||||
CpuRegister.Y -> out(" ldy prog8_regsaveY")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -840,6 +885,7 @@ class AsmGen(private val program: Program,
|
||||
is RepeatLoop -> translate(stmt)
|
||||
is When -> translate(stmt)
|
||||
is AnonymousScope -> translate(stmt)
|
||||
is Pipe -> translatePipeExpression(stmt.expressions, stmt, true, false)
|
||||
is BuiltinFunctionPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore")
|
||||
is UntilLoop -> throw AssemblyError("do..until should have been converted to jumps")
|
||||
is WhileLoop -> throw AssemblyError("while should have been converted to jumps")
|
||||
@@ -945,10 +991,16 @@ class AsmGen(private val program: Program,
|
||||
internal fun translateExpression(expression: Expression) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
internal fun translateBuiltinFunctionCallExpression(functionCallExpr: FunctionCallExpr, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
|
||||
internal fun translateBuiltinFunctionCallExpression(functionCallExpr: FunctionCallExpression, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCallExpr, signature, resultToStack, resultRegister)
|
||||
|
||||
internal fun translateFunctionCall(functionCallExpr: FunctionCallExpr, isExpression: Boolean) =
|
||||
internal fun translateBuiltinFunctionCallExpression(name: String, args: List<AsmAssignSource>, scope: Subroutine): DataType =
|
||||
builtinFunctionsAsmGen.translateFunctioncall(name, args, false, scope)
|
||||
|
||||
internal fun translateBuiltinFunctionCallStatement(name: String, args: List<AsmAssignSource>, scope: Subroutine) =
|
||||
builtinFunctionsAsmGen.translateFunctioncall(name, args, true, scope)
|
||||
|
||||
internal fun translateFunctionCall(functionCallExpr: FunctionCallExpression, isExpression: Boolean) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCallExpr, isExpression)
|
||||
|
||||
internal fun saveXbeforeCall(functionCall: IFunctionCall) =
|
||||
@@ -1053,22 +1105,8 @@ class AsmGen(private val program: Program,
|
||||
memdefs2asm(sub.statements, null)
|
||||
|
||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||
if(sub.name=="start" && sub.definingBlock.name=="main") {
|
||||
out("; program startup initialization")
|
||||
out(" cld")
|
||||
if(!options.dontReinitGlobals) {
|
||||
blockVariableInitializers.forEach {
|
||||
if (it.value.isNotEmpty())
|
||||
out(" jsr ${it.key.name}.prog8_init_vars")
|
||||
}
|
||||
}
|
||||
out("""
|
||||
tsx
|
||||
stx prog8_lib.orig_stackpointer ; required for sys.exit()
|
||||
ldx #255 ; init estack ptr
|
||||
clv
|
||||
clc""")
|
||||
}
|
||||
if(sub.name=="start" && sub.definingBlock.name=="main")
|
||||
entrypointInitialization()
|
||||
|
||||
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
||||
out("; simple int arg(s) passed via register(s)")
|
||||
@@ -1111,12 +1149,12 @@ class AsmGen(private val program: Program,
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
if(sub.asmGenInfo.usedRegsaveA)
|
||||
out("_prog8_regsaveA .byte 0")
|
||||
if(sub.asmGenInfo.usedRegsaveA) // will probably never occur
|
||||
out("prog8_regsaveA .byte 0")
|
||||
if(sub.asmGenInfo.usedRegsaveX)
|
||||
out("_prog8_regsaveX .byte 0")
|
||||
out("prog8_regsaveX .byte 0")
|
||||
if(sub.asmGenInfo.usedRegsaveY)
|
||||
out("_prog8_regsaveY .byte 0")
|
||||
out("prog8_regsaveY .byte 0")
|
||||
if(sub.asmGenInfo.usedFloatEvalResultVar1)
|
||||
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
||||
if(sub.asmGenInfo.usedFloatEvalResultVar2)
|
||||
@@ -1126,6 +1164,63 @@ class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun entrypointInitialization() {
|
||||
out("; program startup initialization")
|
||||
out(" cld")
|
||||
if(!options.dontReinitGlobals) {
|
||||
blockVariableInitializers.forEach {
|
||||
if (it.value.isNotEmpty())
|
||||
out(" jsr ${it.key.name}.prog8_init_vars")
|
||||
}
|
||||
}
|
||||
|
||||
// string and array variables in zeropage that have initializer value, should be initialized
|
||||
val stringVarsInZp = varsInZeropage.filter { it.datatype==DataType.STR && it.value!=null }
|
||||
val arrayVarsInZp = varsInZeropage.filter { it.datatype in ArrayDatatypes && it.value!=null }
|
||||
if(stringVarsInZp.isNotEmpty() || arrayVarsInZp.isNotEmpty()) {
|
||||
out("; zp str and array initializations")
|
||||
stringVarsInZp.forEach {
|
||||
out("""
|
||||
lda #<${it.name}
|
||||
ldy #>${it.name}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${it.name}_init_value
|
||||
ldy #>${it.name}_init_value
|
||||
jsr prog8_lib.strcpy""")
|
||||
}
|
||||
arrayVarsInZp.forEach {
|
||||
val numelements = (it.value as ArrayLiteralValue).value.size
|
||||
out("""
|
||||
lda #<${it.name}_init_value
|
||||
ldy #>${it.name}_init_value
|
||||
sta cx16.r0L
|
||||
sty cx16.r0H
|
||||
lda #<${it.name}
|
||||
ldy #>${it.name}
|
||||
sta cx16.r1L
|
||||
sty cx16.r1H
|
||||
lda #<$numelements
|
||||
ldy #>$numelements
|
||||
jsr sys.memcopy""")
|
||||
}
|
||||
out(" jmp +")
|
||||
}
|
||||
|
||||
stringVarsInZp.forEach {
|
||||
outputStringvar(it, it.name+"_init_value")
|
||||
}
|
||||
arrayVarsInZp.forEach {
|
||||
vardecl2asm(it, it.name+"_init_value")
|
||||
}
|
||||
|
||||
out("""+ tsx
|
||||
stx prog8_lib.orig_stackpointer ; required for sys.exit()
|
||||
ldx #255 ; init estack ptr
|
||||
clv
|
||||
clc""")
|
||||
}
|
||||
|
||||
private fun branchInstruction(condition: BranchCondition, complement: Boolean) =
|
||||
if(complement) {
|
||||
when (condition) {
|
||||
@@ -1395,31 +1490,16 @@ $repeatLabel lda $counterVar
|
||||
|
||||
val counterVar = makeLabel("counter")
|
||||
when(dt) {
|
||||
DataType.UBYTE -> {
|
||||
if(zeropage.hasByteAvailable()) {
|
||||
// allocate count var on ZP
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, stmt.position, errors)
|
||||
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, zpAddr))
|
||||
} else {
|
||||
if(mustBeInZeropage)
|
||||
return null
|
||||
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, null))
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(zeropage.hasWordAvailable()) {
|
||||
// allocate count var on ZP
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, stmt.position, errors)
|
||||
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, zpAddr))
|
||||
} else {
|
||||
if(mustBeInZeropage)
|
||||
return null
|
||||
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, null))
|
||||
}
|
||||
DataType.UBYTE, DataType.UWORD -> {
|
||||
val result = zeropage.allocate(listOf(counterVar), dt, null, stmt.position, errors)
|
||||
result.fold(
|
||||
success = { (address, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
|
||||
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
|
||||
)
|
||||
return counterVar
|
||||
}
|
||||
else -> throw AssemblyError("invalidt dt")
|
||||
}
|
||||
return counterVar
|
||||
}
|
||||
|
||||
private fun translate(stmt: When) {
|
||||
@@ -1467,8 +1547,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun translate(stmt: Label) {
|
||||
// underscore prefix to make sure it's a local label. Is slightly problematic, see GitHub issue #62
|
||||
out("_${stmt.name}")
|
||||
out(stmt.name)
|
||||
}
|
||||
|
||||
private fun translate(scope: AnonymousScope) {
|
||||
@@ -1542,9 +1621,7 @@ $repeatLabel lda $counterVar
|
||||
"%breakpoint" -> {
|
||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||
breakpointLabels.add(label)
|
||||
out("""
|
||||
nop
|
||||
$label nop""")
|
||||
out(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1614,6 +1691,21 @@ $label nop""")
|
||||
assemblyLines.add(assembly)
|
||||
}
|
||||
|
||||
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair {
|
||||
return when (val targetRoutine = it.targetStatement(program)!!) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
when (BuiltinFunctions.getValue(targetRoutine.name).known_returntype) {
|
||||
in ByteDatatypes -> RegisterOrPair.A
|
||||
in WordDatatypes -> RegisterOrPair.AY
|
||||
DataType.FLOAT -> RegisterOrPair.FAC1
|
||||
else -> throw AssemblyError("weird returntype")
|
||||
}
|
||||
}
|
||||
is Subroutine -> targetRoutine.asmReturnvaluesRegisters.single().registerOrPair!!
|
||||
else -> throw AssemblyError("invalid call target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendAYlsb(valueDt: DataType) {
|
||||
// sign extend signed byte in A to full word in AY
|
||||
when(valueDt) {
|
||||
@@ -1658,12 +1750,12 @@ $label nop""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun isZpVar(scopedName: String): Boolean =
|
||||
scopedName in allocatedZeropageVariables
|
||||
internal fun isZpVar(scopedName: List<String>): Boolean =
|
||||
zeropage.allocatedZeropageVariable(scopedName)!=null
|
||||
|
||||
internal fun isZpVar(variable: IdentifierReference): Boolean {
|
||||
val vardecl = variable.targetVarDecl(program)!!
|
||||
return vardecl.scopedName.joinToString(".") in allocatedZeropageVariables
|
||||
return zeropage.allocatedZeropageVariable(vardecl.scopedName)!=null
|
||||
}
|
||||
|
||||
internal fun jmp(asmLabel: String) {
|
||||
@@ -1812,7 +1904,11 @@ $label nop""")
|
||||
operator: String,
|
||||
jumpIfFalseLabel: String
|
||||
) {
|
||||
when(val dt = left.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
val dt = left.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(dt in IntegerDatatypes && left is IdentifierReference)
|
||||
return testVariableZeroAndJump(left, dt, operator, jumpIfFalseLabel)
|
||||
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> {
|
||||
if(operator=="<") {
|
||||
out(" jmp $jumpIfFalseLabel")
|
||||
@@ -1822,7 +1918,7 @@ $label nop""")
|
||||
}
|
||||
if(dt==DataType.UBYTE) {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, false)
|
||||
if (left is FunctionCallExpr && !left.isSimple)
|
||||
if (left is FunctionCallExpression && !left.isSimple)
|
||||
out(" cmp #0")
|
||||
} else {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, false)
|
||||
@@ -1838,7 +1934,7 @@ $label nop""")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, true)
|
||||
if (left is FunctionCallExpr && !left.isSimple)
|
||||
if (left is FunctionCallExpression && !left.isSimple)
|
||||
out(" cmp #0")
|
||||
when (operator) {
|
||||
"==" -> out(" bne $jumpIfFalseLabel")
|
||||
@@ -1896,6 +1992,66 @@ $label nop""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun testVariableZeroAndJump(variable: IdentifierReference, dt: DataType, operator: String, jumpIfFalseLabel: String) {
|
||||
// optimized code if the expression is just an identifier (variable)
|
||||
val varname = asmVariableName(variable)
|
||||
when(dt) {
|
||||
DataType.UBYTE -> when(operator) {
|
||||
"==" -> out(" lda $varname | bne $jumpIfFalseLabel")
|
||||
"!=" -> out(" lda $varname | beq $jumpIfFalseLabel")
|
||||
">" -> out(" lda $varname | beq $jumpIfFalseLabel")
|
||||
"<" -> out(" bra $jumpIfFalseLabel")
|
||||
">=" -> {}
|
||||
"<=" -> out(" lda $varname | bne $jumpIfFalseLabel")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
DataType.BYTE -> when(operator) {
|
||||
"==" -> out(" lda $varname | bne $jumpIfFalseLabel")
|
||||
"!=" -> out(" lda $varname | beq $jumpIfFalseLabel")
|
||||
">" -> out(" lda $varname | beq $jumpIfFalseLabel | bmi $jumpIfFalseLabel")
|
||||
"<" -> out(" lda $varname | bpl $jumpIfFalseLabel")
|
||||
">=" -> out(" lda $varname | bmi $jumpIfFalseLabel")
|
||||
"<=" -> out("""
|
||||
lda $varname
|
||||
beq +
|
||||
bpl $jumpIfFalseLabel
|
||||
+ """)
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
DataType.UWORD -> when(operator) {
|
||||
"==" -> out(" lda $varname | ora $varname+1 | bne $jumpIfFalseLabel")
|
||||
"!=" -> out(" lda $varname | ora $varname+1 | beq $jumpIfFalseLabel")
|
||||
">" -> out(" lda $varname | ora $varname+1 | beq $jumpIfFalseLabel")
|
||||
"<" -> out(" bra $jumpIfFalseLabel")
|
||||
">=" -> {}
|
||||
"<=" -> out(" lda $varname | ora $varname+1 | bne $jumpIfFalseLabel")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
DataType.WORD -> when (operator) {
|
||||
"==" -> out(" lda $varname | bne $jumpIfFalseLabel | lda $varname+1 | bne $jumpIfFalseLabel")
|
||||
"!=" -> out(" lda $varname | ora $varname+1 | beq $jumpIfFalseLabel")
|
||||
">" -> out("""
|
||||
lda $varname+1
|
||||
bmi $jumpIfFalseLabel
|
||||
bne +
|
||||
lda $varname
|
||||
beq $jumpIfFalseLabel
|
||||
+ """)
|
||||
"<" -> out(" lda $varname+1 | bpl $jumpIfFalseLabel")
|
||||
">=" -> out(" lda $varname+1 | bmi $jumpIfFalseLabel")
|
||||
"<=" -> out("""
|
||||
lda $varname+1
|
||||
bmi +
|
||||
bne $jumpIfFalseLabel
|
||||
lda $varname
|
||||
bne $jumpIfFalseLabel
|
||||
+ """)
|
||||
else -> throw AssemblyError("invalid comparison operator $operator")
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun testNonzeroComparisonAndJump(
|
||||
left: Expression,
|
||||
operator: String,
|
||||
@@ -2730,6 +2886,7 @@ $label nop""")
|
||||
if(rightConstVal.number.toInt()!=0) {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
code("#>${rightConstVal.number.toInt()}", "#<${rightConstVal.number.toInt()}")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3383,4 +3540,56 @@ $label nop""")
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translatePipeExpression(expressions: Iterable<Expression>, scope: Node, isStatement: Boolean, pushResultOnEstack: Boolean) {
|
||||
|
||||
// TODO more efficient code generation to avoid needless assignments to the temp var
|
||||
|
||||
// the first term: an expression (could be anything) producing a value.
|
||||
val subroutine = scope.definingSubroutine!!
|
||||
val firstTerm = expressions.first()
|
||||
var valueDt = firstTerm.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
var valueSource: AsmAssignSource =
|
||||
if(firstTerm is IFunctionCall) {
|
||||
val resultReg = returnRegisterOfFunction(firstTerm.target)
|
||||
assignExpressionToRegister(firstTerm, resultReg, valueDt in listOf(DataType.BYTE, DataType.WORD, DataType.FLOAT))
|
||||
AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(firstTerm, program, this)
|
||||
}
|
||||
|
||||
// the 2nd to N-1 terms: unary function calls taking a single param and producing a value.
|
||||
// directly assign their argument from the previous call's returnvalue.
|
||||
expressions.drop(1).dropLast(1).forEach {
|
||||
valueDt = functioncallAsmGen.translateUnaryFunctionCallWithArgSource(it as IdentifierReference, valueSource, false, subroutine)
|
||||
val resultReg = returnRegisterOfFunction(it)
|
||||
valueSource = AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg)
|
||||
}
|
||||
|
||||
// the last term: unary function call taking a single param and optionally producing a result value.
|
||||
if(isStatement) {
|
||||
// the last term in the pipe, don't care about return var:
|
||||
functioncallAsmGen.translateUnaryFunctionCallWithArgSource(
|
||||
expressions.last() as IdentifierReference, valueSource, true, subroutine)
|
||||
} else {
|
||||
// the last term in the pipe, regular function call with returnvalue:
|
||||
valueDt = functioncallAsmGen.translateUnaryFunctionCallWithArgSource(
|
||||
expressions.last() as IdentifierReference, valueSource, false, subroutine)
|
||||
if(pushResultOnEstack) {
|
||||
when (valueDt) {
|
||||
in ByteDatatypes -> {
|
||||
out(" sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
out(" jsr floats.push_fac1")
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,7 +44,7 @@ internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
}
|
||||
}
|
||||
is FunctionCallExpr -> {
|
||||
is FunctionCallExpression -> {
|
||||
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
|
||||
return isClobberRisk(expr.args[0])
|
||||
if (expr.target.nameInSource == listOf("mkword"))
|
||||
|
||||
@@ -13,13 +13,14 @@ import prog8.ast.toHex
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.*
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CpuType
|
||||
import prog8.compilerinterface.FSignature
|
||||
import prog8.compilerinterface.subroutineFloatEvalResultVar2
|
||||
|
||||
|
||||
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
|
||||
|
||||
internal fun translateFunctioncallExpression(fcall: FunctionCallExpr, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
internal fun translateFunctioncallExpression(fcall: FunctionCallExpression, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
|
||||
}
|
||||
|
||||
@@ -27,6 +28,37 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
|
||||
}
|
||||
|
||||
internal fun translateFunctioncall(name: String, args: List<AsmAssignSource>, isStatement: Boolean, scope: Subroutine): DataType {
|
||||
val func = BuiltinFunctions.getValue(name)
|
||||
val argExpressions = args.map { src ->
|
||||
when(src.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> src.number!!
|
||||
SourceStorageKind.EXPRESSION -> src.expression!!
|
||||
SourceStorageKind.ARRAY -> src.array!!
|
||||
else -> {
|
||||
// TODO make it so that we can assign efficiently from something else as an expression....namely: register(s)
|
||||
// this is useful in pipe expressions for instance, to skip the use of a temporary variable
|
||||
// but for now, just assign it to a temporary variable and use that as a source
|
||||
val tempvar = asmgen.getTempVarName(src.datatype)
|
||||
val assignTempvar = AsmAssignment(
|
||||
src,
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, src.datatype, scope, variableAsmName = asmgen.asmVariableName(tempvar)),
|
||||
false, program.memsizer, Position.DUMMY
|
||||
)
|
||||
assignAsmGen.translateNormalAssignment(assignTempvar)
|
||||
// now use an expression to assign this tempvar
|
||||
val ident = IdentifierReference(tempvar, Position.DUMMY)
|
||||
ident.linkParents(scope)
|
||||
ident
|
||||
}
|
||||
}
|
||||
}.toMutableList()
|
||||
val fcall = FunctionCallExpression(IdentifierReference(listOf(name), Position.DUMMY), argExpressions, Position.DUMMY)
|
||||
fcall.linkParents(scope)
|
||||
translateFunctioncall(fcall, func, discardResult = false, resultToStack = false, null)
|
||||
return if(isStatement) DataType.UNDEFINED else func.known_returntype!!
|
||||
}
|
||||
|
||||
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if (discardResult && func.pure)
|
||||
return // can just ignore the whole function call altogether
|
||||
@@ -66,6 +98,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"peekw" -> funcPeekW(fcall, resultToStack, resultRegister)
|
||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||
"pokew" -> funcPokeW(fcall)
|
||||
"pokemon" -> { /* meme function */ }
|
||||
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
|
||||
"push", "pushw" -> funcPush(fcall, func)
|
||||
"pop", "popw" -> funcPop(fcall, func)
|
||||
@@ -414,7 +447,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if(discardResult || fcall !is FunctionCallExpr)
|
||||
if(discardResult || fcall !is FunctionCallExpression)
|
||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||
val nameRef = fcall.args[0] as IdentifierReference
|
||||
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
|
||||
|
||||
@@ -11,7 +11,7 @@ import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CpuType
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val functioncallAsmGen: FunctionCallAsmGen) {
|
||||
|
||||
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
||||
internal fun translateExpression(expression:Expression) {
|
||||
@@ -36,16 +36,17 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
||||
is NumericLiteralValue -> translateExpression(expression)
|
||||
is IdentifierReference -> translateExpression(expression)
|
||||
is FunctionCallExpr -> translateFunctionCallResultOntoStack(expression)
|
||||
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
|
||||
is PipeExpression -> asmgen.translatePipeExpression(expression.expressions, expression,false, true)
|
||||
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
|
||||
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||
is RangeExpression -> throw AssemblyError("range expression should have been changed into array values")
|
||||
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
|
||||
else -> TODO("missing expression asmgen for $expression")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpr) {
|
||||
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) {
|
||||
// only for use in nested expression evaluation
|
||||
|
||||
val sub = call.target.targetStatement(program)
|
||||
@@ -539,6 +540,13 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
}
|
||||
in ComparisonOperators -> {
|
||||
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if(rightVal==0)
|
||||
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||
@@ -561,6 +569,69 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) {
|
||||
translateExpressionInternal(expr)
|
||||
when(operator) {
|
||||
"==" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.equal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
"!=" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.notequalzero_b")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.notequalzero_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.notequal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
"<" -> {
|
||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||
return translateExpressionInternal(NumericLiteralValue.fromBoolean(false, expr.position))
|
||||
when(dt) {
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.less_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
">" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.greaterzero_ub")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterzero_sb")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.greaterzero_uw")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterzero_sw")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.greater_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
"<=" -> {
|
||||
when(dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lessequalzeros_b")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lessequalzero_sw")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.lessequal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
">=" -> {
|
||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||
return translateExpressionInternal(NumericLiteralValue.fromBoolean(true, expr.position))
|
||||
when(dt) {
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.greaterequal_zero")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
|
||||
val asmVar = asmgen.asmVariableName(variable)
|
||||
when(dt) {
|
||||
@@ -631,7 +702,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
|
||||
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue) | jsr floats.push_float")
|
||||
}
|
||||
else -> throw AssemblyError("weird element type")
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ArrayToElementTypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.RegisterOrPair
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.expressions.RangeExpression
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.toHex
|
||||
import prog8.codegen.target.AssemblyError
|
||||
@@ -18,10 +19,10 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
if(!iterableDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
when(stmt.iterable) {
|
||||
is RangeExpr -> {
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||
is RangeExpression -> {
|
||||
val range = (stmt.iterable as RangeExpression).toConstantIntegerRange()
|
||||
if(range==null) {
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpr)
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpression)
|
||||
} else {
|
||||
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
|
||||
}
|
||||
@@ -33,7 +34,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) {
|
||||
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||
@@ -288,13 +289,15 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||
// allocate index var on ZP
|
||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||
if(length>=16) {
|
||||
// allocate index var on ZP if possible
|
||||
val result = asmgen.zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("""
|
||||
$indexVar .byte 0""")
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
@@ -327,13 +330,15 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||
// allocate index var on ZP
|
||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||
if(length>=16) {
|
||||
// allocate index var on ZP if possible
|
||||
val result = asmgen.zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
)
|
||||
} else {
|
||||
asmgen.out("""
|
||||
$indexVar .byte 0""")
|
||||
asmgen.out("$indexVar .byte 0")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
@@ -587,7 +592,7 @@ $loopLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpression) =
|
||||
asmgen.assignExpressionToVariable(
|
||||
range.from,
|
||||
asmgen.asmVariableName(stmt.loopVar),
|
||||
|
||||
@@ -4,7 +4,10 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
@@ -126,9 +129,67 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
}
|
||||
|
||||
internal fun translateUnaryFunctionCallWithArgSource(target: IdentifierReference, arg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
|
||||
when(val targetStmt = target.targetStatement(program)!!) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
return if(isStatement) {
|
||||
asmgen.translateBuiltinFunctionCallStatement(targetStmt.name, listOf(arg), scope)
|
||||
DataType.UNDEFINED
|
||||
} else {
|
||||
asmgen.translateBuiltinFunctionCallExpression(targetStmt.name, listOf(arg), scope)
|
||||
}
|
||||
}
|
||||
is Subroutine -> {
|
||||
val argDt = targetStmt.parameters.single().type
|
||||
if(targetStmt.isAsmSubroutine) {
|
||||
// argument via registers
|
||||
val argRegister = targetStmt.asmParameterRegisters.single().registerOrPair!!
|
||||
val assignArgument = AsmAssignment(
|
||||
arg,
|
||||
AsmAssignTarget.fromRegisters(argRegister, argDt in SignedDatatypes, scope, program, asmgen),
|
||||
false, program.memsizer, target.position
|
||||
)
|
||||
asmgen.translateNormalAssignment(assignArgument)
|
||||
} else {
|
||||
val assignArgument: AsmAssignment =
|
||||
if(optimizeIntArgsViaRegisters(targetStmt)) {
|
||||
// argument goes via registers as optimization
|
||||
val paramReg: RegisterOrPair = when(argDt) {
|
||||
in ByteDatatypes -> RegisterOrPair.A
|
||||
in WordDatatypes -> RegisterOrPair.AY
|
||||
DataType.FLOAT -> RegisterOrPair.FAC1
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
AsmAssignment(
|
||||
arg,
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, argDt, scope, register = paramReg),
|
||||
false, program.memsizer, target.position
|
||||
)
|
||||
} else {
|
||||
// arg goes via parameter variable
|
||||
val argVarName = asmgen.asmVariableName(targetStmt.scopedName + targetStmt.parameters.single().name)
|
||||
AsmAssignment(
|
||||
arg,
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, argDt, scope, argVarName),
|
||||
false, program.memsizer, target.position
|
||||
)
|
||||
}
|
||||
asmgen.translateNormalAssignment(assignArgument)
|
||||
}
|
||||
if(targetStmt.shouldSaveX())
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope)
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(target)}")
|
||||
if(targetStmt.shouldSaveX())
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
return if(isStatement) DataType.UNDEFINED else targetStmt.returntypes.single()
|
||||
}
|
||||
else -> throw AssemblyError("invalid call target")
|
||||
}
|
||||
}
|
||||
|
||||
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
|
||||
for(arg in sub.parameters.withIndex().zip(call.args))
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
argumentViaVariable(sub, arg.first.value, arg.second)
|
||||
}
|
||||
|
||||
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
|
||||
@@ -272,15 +333,15 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
asmgen.out(" plp") // set the carry flag back to correct value
|
||||
}
|
||||
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: SubroutineParameter, value: Expression) {
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + parameter.value.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + parameter.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.type, sub)
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
|
||||
|
||||
@@ -83,7 +83,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||
asmgen.out(" lda #<($asmArrayvarname+$indexValue) | ldy #>($asmArrayvarname+$indexValue)")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
|
||||
@@ -167,7 +167,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
}
|
||||
is FunctionCallExpr -> {
|
||||
is FunctionCallExpression -> {
|
||||
when (val sub = value.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
|
||||
|
||||
+83
-50
@@ -88,7 +88,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue")
|
||||
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue)")
|
||||
assignFloatFromAY(assign.target)
|
||||
}
|
||||
else ->
|
||||
@@ -159,7 +159,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
|
||||
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
|
||||
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
|
||||
is FunctionCallExpr -> {
|
||||
is FunctionCallExpression -> {
|
||||
when (val sub = value.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
asmgen.saveXbeforeCall(value)
|
||||
@@ -275,16 +275,52 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
containmentCheckIntoA(value)
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
}
|
||||
is PipeExpression -> {
|
||||
asmgen.translatePipeExpression(value.expressions, value, false, false)
|
||||
val resultDt = value.inferType(program)
|
||||
val register =
|
||||
if(resultDt.isBytes) RegisterOrPair.A
|
||||
else if(resultDt.isWords) RegisterOrPair.AY
|
||||
else if(resultDt istype DataType.FLOAT) RegisterOrPair.FAC1
|
||||
else throw AssemblyError("invalid dt")
|
||||
asmgen.assignRegister(register, assign.target)
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
if(value.operator in ComparisonOperators) {
|
||||
// TODO real optimized code for comparison expressions that yield a boolean result value
|
||||
// for now generate code for this: assign-false; if expr { assign-true }
|
||||
translateNormalAssignment(
|
||||
AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, DataType.UBYTE, number=NumericLiteralValue.fromBoolean(false, assign.position)),
|
||||
assign.target, false, program.memsizer, assign.position
|
||||
)
|
||||
)
|
||||
val origTarget = assign.target.origAstTarget
|
||||
if(origTarget!=null) {
|
||||
val assignTrue = AnonymousScope(mutableListOf(
|
||||
Assignment(origTarget, NumericLiteralValue.fromBoolean(true, assign.position), AssignmentOrigin.ASMGEN, assign.position)
|
||||
), assign.position)
|
||||
val assignFalse = AnonymousScope(mutableListOf(), assign.position)
|
||||
val ifelse = IfElse(value.copy(), assignTrue, assignFalse, assign.position)
|
||||
ifelse.linkParents(value)
|
||||
asmgen.translate(ifelse)
|
||||
}
|
||||
else {
|
||||
// no orig ast assign target, can't use the workaround, so fallback to stack eval
|
||||
fallbackToStackEval(value, assign)
|
||||
}
|
||||
} else {
|
||||
// Everything else just evaluate via the stack.
|
||||
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
|
||||
// because the code here is the implementation of exactly that...)
|
||||
fallbackToStackEval(value, assign)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Everything else just evaluate via the stack.
|
||||
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
|
||||
// because the code here is the implementation of exactly that...)
|
||||
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||
asmgen.translateExpression(value)
|
||||
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
|
||||
assignStackValue(assign.target)
|
||||
fallbackToStackEval(value, assign)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,9 +334,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun fallbackToStackEval(value: Expression, assign: AsmAssignment) {
|
||||
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||
asmgen.translateExpression(value)
|
||||
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||
if (assign.target.kind != TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
|
||||
assignStackValue(assign.target)
|
||||
}
|
||||
|
||||
private fun containmentCheckIntoA(containment: ContainmentCheck) {
|
||||
val elementDt = containment.element.inferType(program)
|
||||
val range = containment.iterable as? RangeExpr
|
||||
val range = containment.iterable as? RangeExpression
|
||||
if(range!=null) {
|
||||
val constRange = range.toConstantIntegerRange()
|
||||
if(constRange!=null)
|
||||
@@ -311,12 +356,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
if(variable!=null) {
|
||||
if(elementDt istype DataType.FLOAT)
|
||||
throw AssemblyError("containment check of floats not supported")
|
||||
if(variable.autogeneratedDontRemove) {
|
||||
if(variable.origin!=VarDeclOrigin.USERCODE) {
|
||||
when(variable.datatype) {
|
||||
DataType.STR -> {
|
||||
require(elementDt.isBytes)
|
||||
val stringVal = variable.value as StringLiteralValue
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.encoding)
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
@@ -365,7 +410,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val stringVal = containment.iterable as? StringLiteralValue
|
||||
if(stringVal!=null) {
|
||||
require(elementDt.isBytes)
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.encoding)
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
|
||||
}
|
||||
val arrayVal = containment.iterable as? ArrayLiteralValue
|
||||
@@ -596,7 +641,7 @@ $containsLabel lda #1
|
||||
}
|
||||
|
||||
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
|
||||
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
|
||||
lsb.linkParents(value.parent)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
|
||||
val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
|
||||
@@ -940,8 +985,8 @@ $containsLabel lda #1
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
lda #<(${target.asmVarname}+$scaledIdx)
|
||||
ldy #>(${target.asmVarname}+$scaledIdx)
|
||||
jsr floats.pop_float
|
||||
""")
|
||||
}
|
||||
@@ -1153,8 +1198,8 @@ $containsLabel lda #1
|
||||
ldy #>$sourceName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
lda #<(${target.asmVarname}+$scaledIdx)
|
||||
ldy #>(${target.asmVarname}+$scaledIdx)
|
||||
jsr floats.copy_float
|
||||
""")
|
||||
}
|
||||
@@ -1306,17 +1351,13 @@ $containsLabel lda #1
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname}
|
||||
lda $sourceName+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $sourceName+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $sourceName+3
|
||||
sta ${target.asmVarname}+3
|
||||
lda $sourceName+4
|
||||
sta ${target.asmVarname}+4
|
||||
""")
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.out("""
|
||||
@@ -2037,17 +2078,13 @@ $containsLabel lda #1
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta ${target.asmVarname}
|
||||
lda $constFloat+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $constFloat+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $constFloat+3
|
||||
sta ${target.asmVarname}+3
|
||||
lda $constFloat+4
|
||||
sta ${target.asmVarname}+4
|
||||
""")
|
||||
lda #<$constFloat
|
||||
ldy #>$constFloat
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val arrayVarName = target.asmVarname
|
||||
@@ -2055,17 +2092,13 @@ $containsLabel lda #1
|
||||
if (constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta $arrayVarName+$indexValue
|
||||
lda $constFloat+1
|
||||
sta $arrayVarName+$indexValue+1
|
||||
lda $constFloat+2
|
||||
sta $arrayVarName+$indexValue+2
|
||||
lda $constFloat+3
|
||||
sta $arrayVarName+$indexValue+3
|
||||
lda $constFloat+4
|
||||
sta $arrayVarName+$indexValue+4
|
||||
""")
|
||||
lda #<$constFloat
|
||||
ldy #>$constFloat
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<($arrayVarName+$indexValue)
|
||||
ldy #>($arrayVarName+$indexValue)
|
||||
jsr floats.copy_float""")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||
asmgen.out("""
|
||||
|
||||
+5
-5
@@ -744,18 +744,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
|
||||
"==" -> {
|
||||
asmgen.out("""
|
||||
lda #$value
|
||||
cmp $name
|
||||
lda $name
|
||||
cmp #$value
|
||||
beq +
|
||||
lda #0
|
||||
bne ++
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta $name""")
|
||||
}
|
||||
"!=" -> {
|
||||
asmgen.out("""
|
||||
lda #$value
|
||||
cmp $name
|
||||
lda $name
|
||||
cmp #$value
|
||||
beq +
|
||||
lda #1
|
||||
bne ++
|
||||
|
||||
@@ -67,18 +67,6 @@ class 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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.codegen.target.cx16
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import prog8.compilerinterface.Zeropage
|
||||
@@ -37,5 +38,15 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables[listOf("cx16", "r${reg}")] = (2+reg*2).toUInt() to (DataType.UWORD to 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables[listOf("cx16", "r${reg}s")] = (2+reg*2).toUInt() to (DataType.WORD to 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables[listOf("cx16", "r${reg}L")] = (2+reg*2).toUInt() to (DataType.UBYTE to 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables[listOf("cx16", "r${reg}H")] = (3+reg*2).toUInt() to (DataType.UBYTE to 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = (2+reg*2).toUInt() to (DataType.BYTE to 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = (3+reg*2).toUInt() to (DataType.BYTE to 1) // cx16.r0sH .. cx16.r15sH
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.AugmentAssignmentOperators
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.AssignmentOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
@@ -45,7 +46,7 @@ X = BinExpr X = LeftExpr
|
||||
|
||||
|
||||
*/
|
||||
if(binExpr.operator in AugmentAssignmentOperators + listOf("==", "!=") && isSimpleTarget(assignment.target)) {
|
||||
if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||
if(assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
if(assignment.target isSameAs binExpr.left) {
|
||||
@@ -73,17 +74,8 @@ X = BinExpr X = LeftExpr
|
||||
// )
|
||||
}
|
||||
|
||||
if(binExpr.operator == "==" || binExpr.operator == "!=") {
|
||||
// don't split if the operand(s) don't fit the type of the resulting variable
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val leftDt = binExpr.left.inferType(program)
|
||||
val rightDt = binExpr.right.inferType(program)
|
||||
if(leftDt isNotAssignableTo targetDt || rightDt isNotAssignableTo targetDt)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(binExpr.right.isSimple) {
|
||||
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
|
||||
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, AssignmentOrigin.OPTIMIZER, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
return listOf(
|
||||
@@ -116,7 +108,7 @@ X = BinExpr X = LeftExpr
|
||||
}
|
||||
val assignTempVar = Assignment(
|
||||
AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position),
|
||||
typecast.expression, typecast.position
|
||||
typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
|
||||
|
||||
@@ -310,7 +310,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
// the args of a fuction are constfolded via recursion already.
|
||||
val constvalue = functionCallExpr.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
@@ -320,7 +320,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr? {
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpression): RangeExpression? {
|
||||
val fromCast = rangeFrom.cast(targetDt)
|
||||
val toCast = rangeTo.cast(targetDt)
|
||||
if(!fromCast.isValid || !toCast.isValid)
|
||||
@@ -337,11 +337,11 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
range.step
|
||||
}
|
||||
|
||||
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
|
||||
return RangeExpression(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
|
||||
}
|
||||
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
|
||||
val iterableRange = forLoop.iterable as? RangeExpression ?: return noModifications
|
||||
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||
if(rangeFrom==null || rangeTo==null) return noModifications
|
||||
|
||||
@@ -25,7 +25,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
&& declConstValue.type != decl.datatype) {
|
||||
// avoid silent float roundings
|
||||
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
|
||||
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position)
|
||||
errors.err("refused rounding of float to avoid loss of precision", decl.value!!.position)
|
||||
} else {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
@@ -40,7 +40,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
|
||||
val from = range.from.constValue(program)?.number
|
||||
val to = range.to.constValue(program)?.number
|
||||
val step = range.step.constValue(program)?.number
|
||||
@@ -139,7 +139,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
@@ -193,7 +193,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
|
||||
@@ -8,12 +8,10 @@ import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.IntegerDatatypes
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.Jump
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
@@ -26,7 +24,7 @@ import kotlin.math.pow
|
||||
*/
|
||||
|
||||
|
||||
class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
|
||||
@@ -198,28 +196,6 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed < 0 --> signed & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw < 0 --> msb(signedw) & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// simplify when a term is constant and directly determines the outcome
|
||||
@@ -301,7 +277,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallExpr.target.nameInSource == listOf("lsb")) {
|
||||
val arg = functionCallExpr.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
@@ -345,7 +321,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
val range = containment.iterable as? RangeExpr
|
||||
val range = containment.iterable as? RangeExpression
|
||||
if(range!=null && range.step.constValue(program)?.number==1.0) {
|
||||
val from = range.from.constValue(program)
|
||||
val to = range.to.constValue(program)
|
||||
@@ -363,6 +339,44 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> {
|
||||
val expressions = pipeExpr.expressions
|
||||
val firstValue = expressions.first()
|
||||
if(firstValue.isSimple) {
|
||||
val funcname = expressions[1] as IdentifierReference
|
||||
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
|
||||
val newExprs = mutableListOf<Expression>(first)
|
||||
newExprs.addAll(expressions.drop(2))
|
||||
return listOf(IAstModification.ReplaceNode(pipeExpr, PipeExpression(newExprs, pipeExpr.position), parent))
|
||||
}
|
||||
val singleExpr = expressions.singleOrNull()
|
||||
if(singleExpr!=null) {
|
||||
val callExpr = singleExpr as FunctionCallExpression
|
||||
val call = FunctionCallExpression(callExpr.target, callExpr.args, callExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(pipeExpr, call, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(pipe: Pipe, parent: Node): Iterable<IAstModification> {
|
||||
val expressions = pipe.expressions
|
||||
val firstValue = expressions.first()
|
||||
if(firstValue.isSimple) {
|
||||
val funcname = expressions[1] as IdentifierReference
|
||||
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
|
||||
val newExprs = mutableListOf<Expression>(first)
|
||||
newExprs.addAll(expressions.drop(2))
|
||||
return listOf(IAstModification.ReplaceNode(pipe, Pipe(newExprs, pipe.position), parent))
|
||||
}
|
||||
val singleExpr = expressions.singleOrNull()
|
||||
if(singleExpr!=null) {
|
||||
val callExpr = singleExpr as FunctionCallExpression
|
||||
val call = FunctionCallStatement(callExpr.target, callExpr.args, true, callExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(pipe, call, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||
return when {
|
||||
subBinExpr.left isSameAs x -> subBinExpr.right
|
||||
@@ -474,7 +488,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
0.5 -> {
|
||||
// sqrt(left)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
return FunctionCallExpression(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
// left
|
||||
@@ -667,12 +681,12 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue(targetDt, 0.0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@@ -709,11 +723,11 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
}
|
||||
else if (amount >= 8) {
|
||||
val msb = FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
// mkword(0, msb(v))
|
||||
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
|
||||
return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
|
||||
}
|
||||
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
return optimizationCount
|
||||
}
|
||||
|
||||
fun Program.simplifyExpressions() : Int {
|
||||
val opti = ExpressionSimplifier(this)
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class StatementOptimizer(private val program: Program,
|
||||
private val compTarget: ICompilationTarget
|
||||
) : AstWalker() {
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with a simple value,
|
||||
// remove the jump altogeter and inline the returnvalue directly.
|
||||
|
||||
@@ -77,7 +77,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(string!=null) {
|
||||
val pos = functionCallStatement.position
|
||||
if (string.value.length == 1) {
|
||||
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
val firstCharEncoded = compTarget.encodeString(string.value, string.encoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
|
||||
@@ -85,7 +85,7 @@ class StatementOptimizer(private val program: Program,
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
} else if (string.value.length == 2) {
|
||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.encoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
|
||||
@@ -120,7 +120,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
|
||||
val name = getTempVarName(arg.inferType(program))
|
||||
val tempvar = IdentifierReference(name, functionCallStatement.position)
|
||||
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, functionCallStatement.position)
|
||||
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
|
||||
@@ -177,13 +177,13 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
val range = forLoop.iterable as? RangeExpression
|
||||
if(range!=null) {
|
||||
if (range.size() == 1) {
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, forLoop.position))
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
@@ -195,10 +195,10 @@ class StatementOptimizer(private val program: Program,
|
||||
val size = sv.value.length
|
||||
if(size==1) {
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
|
||||
val character = compTarget.encodeString(sv.value, sv.encoding)[0]
|
||||
val byte = NumericLiteralValue(DataType.UBYTE, character.toDouble(), iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
@@ -212,7 +212,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(
|
||||
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
|
||||
forLoop.position))
|
||||
AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
@@ -275,14 +275,9 @@ class StatementOptimizer(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.parent as IStatementContainer
|
||||
val label = jump.identifier?.targetStatement(program)
|
||||
if (label != null && scope.statements.indexOf(label) == scope.statements.indexOf(jump) + 1)
|
||||
return listOf(IAstModification.Remove(jump, scope))
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// NOTE: do NOT remove a jump to the next statement, because this will lead to wrong code when this occurs at the end of a subroutine
|
||||
// if we want to optimize this away, it can be done later at code generation time.
|
||||
|
||||
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
||||
// if the next statement is return with no returnvalue, change into a regular jump if there are no parameters as well.
|
||||
@@ -323,7 +318,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val addConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
@@ -334,7 +329,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val subConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
@@ -375,7 +370,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(bexpr.right isSameAs assignment.target) {
|
||||
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
|
||||
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
|
||||
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), assignment.position)
|
||||
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(bexpr, negation, assignment),
|
||||
IAstModification.InsertAfter(assignment, addValue, parent as IStatementContainer)
|
||||
@@ -454,7 +449,7 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
val returnValueIntermediary = IdentifierReference(listOf("prog8_lib", returnVarName), returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, AssignmentOrigin.OPTIMIZER, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
||||
|
||||
@@ -102,27 +102,25 @@ class UnusedCodeRemover(private val program: Program,
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.type==VarDeclType.VAR) {
|
||||
val forceOutput = "force_output" in decl.definingBlock.options()
|
||||
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
|
||||
if (!forceOutput && decl.origin==VarDeclOrigin.USERCODE && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
|
||||
val usages = callgraph.usages(decl)
|
||||
if (usages.isEmpty()) {
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
|
||||
} else {
|
||||
// if all usages are just an assignment to this vardecl,
|
||||
// and it is in regular RAM, then remove the var as well including all assignments
|
||||
val assignTargets = usages.mapNotNull {
|
||||
it.parent as? AssignTarget
|
||||
}.filter {
|
||||
!it.isIOAddress(compTarget.machine)
|
||||
}
|
||||
if(assignTargets.size==usages.size) {
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
val assignmentsToRemove = assignTargets.map { it.parent to it.parent.parent as IStatementContainer}.toSet()
|
||||
return assignmentsToRemove.map {
|
||||
IAstModification.Remove(it.first, it.second)
|
||||
} + listOf(
|
||||
IAstModification.Remove(decl, parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
else {
|
||||
if(usages.size==1) {
|
||||
val singleUse = usages[0].parent
|
||||
if(singleUse is AssignTarget) {
|
||||
val assignment = singleUse.parent as Assignment
|
||||
if(assignment.value !is IFunctionCall) {
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(
|
||||
IAstModification.Remove(decl, parent as IStatementContainer),
|
||||
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +130,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> {
|
||||
// removes 'duplicate' assignments that assign the same target directly after another
|
||||
// removes 'duplicate' assignments that assign the same target directly after another, unless it is a function call
|
||||
val linesToRemove = mutableListOf<Assignment>()
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
@@ -234,8 +232,11 @@ class UnusedCodeRemover(private val program: Program,
|
||||
is PrefixExpression,
|
||||
is BinaryExpression,
|
||||
is TypecastExpression,
|
||||
is FunctionCallExpr -> { /* don't remove */ }
|
||||
else -> linesToRemove.add(assign1)
|
||||
is FunctionCallExpression -> { /* don't remove */ }
|
||||
else -> {
|
||||
if(assign1.value !is IFunctionCall)
|
||||
linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,15 +248,25 @@ pop_float_fac1 .proc
|
||||
.pend
|
||||
|
||||
copy_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; -- copies the 5 bytes of the mflt value pointed to by P8ZP_SCRATCH_W1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
sta _target+1
|
||||
sty _target+2
|
||||
ldy #4
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
_target sta $ffff,y ; modified
|
||||
dey
|
||||
bpl _loop
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
@@ -678,3 +688,54 @@ set_array_float .proc
|
||||
.pend
|
||||
|
||||
|
||||
equal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
beq _true
|
||||
bne _false
|
||||
_true lda #1
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
_false lda #0
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
notequal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
bne equal_zero._true
|
||||
beq equal_zero._false
|
||||
.pend
|
||||
|
||||
greater_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
beq equal_zero._false
|
||||
bpl equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
||||
less_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
bmi equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
||||
greaterequal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
bpl equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
|
||||
lessequal_zero .proc
|
||||
jsr floats.pop_float_fac1
|
||||
jsr floats.SIGN
|
||||
beq equal_zero._true
|
||||
bmi equal_zero._true
|
||||
jmp equal_zero._false
|
||||
.pend
|
||||
@@ -163,7 +163,7 @@ graphics {
|
||||
lda addr+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
ldy separate_pixels
|
||||
lda _filled_right,y
|
||||
lda hline_filled_right,y
|
||||
eor #255
|
||||
ldy #0
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
@@ -207,18 +207,18 @@ _modified stx $ffff ; modified
|
||||
_zero ldx P8ZP_SCRATCH_REG
|
||||
|
||||
ldy separate_pixels
|
||||
beq _zero2
|
||||
beq hline_zero2
|
||||
lda _modified+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda _modified+2
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda _filled_right,y
|
||||
lda hline_filled_right,y
|
||||
ldy #0
|
||||
ora (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
jmp _zero2
|
||||
_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110
|
||||
_zero2
|
||||
jmp hline_zero2
|
||||
hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110
|
||||
hline_zero2
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,13 +169,26 @@ sub color2 (ubyte txtcol, ubyte bgcol) {
|
||||
}
|
||||
|
||||
sub lowercase() {
|
||||
cx16.screen_set_charset(3, 0) ; lowercase charset
|
||||
c64.CHROUT($0e)
|
||||
; this is not 100% compatible: cx16.screen_set_charset(3, 0) ; lowercase petscii charset
|
||||
}
|
||||
|
||||
sub uppercase() {
|
||||
cx16.screen_set_charset(2, 0) ; uppercase charset
|
||||
c64.CHROUT($8e)
|
||||
; this is not 100% compatible: cx16.screen_set_charset(2, 0) ; uppercase petscii charset
|
||||
}
|
||||
|
||||
sub iso() {
|
||||
c64.CHROUT($0f)
|
||||
; This doesn't enable it completely: cx16.screen_set_charset(1, 0) ; iso charset
|
||||
}
|
||||
|
||||
sub iso_off() {
|
||||
; -- you have to call this first when switching back from iso charset to regular charset.
|
||||
c64.CHROUT($8f)
|
||||
}
|
||||
|
||||
|
||||
asmsub scroll_left() clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
|
||||
@@ -242,18 +242,18 @@ close_end:
|
||||
void c64.CHKIN(11) ; use #11 as input channel again
|
||||
%asm {{
|
||||
lda bufferpointer
|
||||
sta _in_buffer+1
|
||||
sta m_in_buffer+1
|
||||
lda bufferpointer+1
|
||||
sta _in_buffer+2
|
||||
sta m_in_buffer+2
|
||||
}}
|
||||
repeat num_bytes {
|
||||
%asm {{
|
||||
jsr c64.CHRIN
|
||||
sta cx16.r5
|
||||
_in_buffer sta $ffff
|
||||
inc _in_buffer+1
|
||||
m_in_buffer sta $ffff
|
||||
inc m_in_buffer+1
|
||||
bne +
|
||||
inc _in_buffer+2
|
||||
inc m_in_buffer+2
|
||||
+ inc list_blocks
|
||||
bne +
|
||||
inc list_blocks+1
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
|
||||
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
|
||||
|
||||
read_byte_from_address_on_stack .proc
|
||||
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
|
||||
lda P8ESTACK_LO+1,x
|
||||
@@ -683,8 +685,106 @@ shiftright_b .proc
|
||||
.pend
|
||||
|
||||
|
||||
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
|
||||
equalzero_b .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq _true
|
||||
bne _false
|
||||
_true lda #1
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
_false lda #0
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
equalzero_w .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora P8ESTACK_HI+1,x
|
||||
beq equalzero_b._true
|
||||
bne equalzero_b._false
|
||||
.pend
|
||||
|
||||
notequalzero_b .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq equalzero_b._false
|
||||
bne equalzero_b._true
|
||||
.pend
|
||||
|
||||
notequalzero_w .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora P8ESTACK_HI+1,x
|
||||
beq equalzero_b._false
|
||||
bne equalzero_b._true
|
||||
.pend
|
||||
|
||||
lesszero_b .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
bmi equalzero_b._true
|
||||
jmp equalzero_b._false
|
||||
.pend
|
||||
|
||||
lesszero_w .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi equalzero_b._true
|
||||
jmp equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterzero_ub .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne equalzero_b._true
|
||||
beq equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterzero_sb .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
beq equalzero_b._false
|
||||
bpl equalzero_b._true
|
||||
bmi equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterzero_uw .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora P8ESTACK_HI+1,x
|
||||
bne equalzero_b._true
|
||||
beq equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterzero_sw .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi equalzero_b._false
|
||||
ora P8ESTACK_LO+1,x
|
||||
beq equalzero_b._false
|
||||
bne equalzero_b._true
|
||||
.pend
|
||||
|
||||
lessequalzero_sb .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
bmi equalzero_b._true
|
||||
beq equalzero_b._true
|
||||
bne equalzero_b._false
|
||||
.pend
|
||||
|
||||
lessequalzero_sw .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi equalzero_b._true
|
||||
ora P8ESTACK_LO+1,x
|
||||
beq equalzero_b._true
|
||||
bne equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterequalzero_sb .proc
|
||||
lda P8ESTACK_LO+1,x
|
||||
bpl equalzero_b._true
|
||||
bmi equalzero_b._false
|
||||
.pend
|
||||
|
||||
greaterequalzero_sw .proc
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi equalzero_b._false
|
||||
ora P8ESTACK_LO+1,x
|
||||
beq equalzero_b._true
|
||||
bne equalzero_b._false
|
||||
.pend
|
||||
|
||||
|
||||
memcopy16_up .proc
|
||||
|
||||
@@ -1 +1 @@
|
||||
7.6
|
||||
7.7
|
||||
@@ -235,6 +235,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
.asSequence()
|
||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||
.map { (it as Directive).args }
|
||||
.filter { it.size==2 && it[0].int!=null && it[1].int!=null }
|
||||
.map { it[0].int!!..it[1].int!! }
|
||||
.toList()
|
||||
|
||||
@@ -282,7 +283,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
|
||||
errors.report()
|
||||
program.addTypecasts(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.variousCleanups(program, errors)
|
||||
program.variousCleanups(program, errors, compilerOptions)
|
||||
errors.report()
|
||||
program.checkValid(errors, compilerOptions)
|
||||
errors.report()
|
||||
@@ -300,7 +301,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = program.simplifyExpressions()
|
||||
val optsDone1 = program.simplifyExpressions(errors)
|
||||
val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget)
|
||||
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
||||
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
@@ -315,7 +316,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
||||
program.desugaring(errors)
|
||||
program.addTypecasts(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.variousCleanups(program, errors)
|
||||
program.variousCleanups(program, errors, compilerOptions)
|
||||
val callGraph = CallGraph(program)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.report()
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
@@ -88,7 +85,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
|
||||
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
|
||||
fun checkUnsignedLoopDownto0(range: RangeExpression?) {
|
||||
if(range==null)
|
||||
return
|
||||
val step = range.step.constValue(program)?.number ?: 1.0
|
||||
@@ -100,7 +97,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
val iterableDt = forLoop.iterable.inferType(program).getOr(DataType.BYTE)
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpression) {
|
||||
errors.err("can only loop over an iterable type", forLoop.position)
|
||||
} else {
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program)
|
||||
@@ -112,14 +109,14 @@ internal class AstChecker(private val program: Program,
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
||||
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
|
||||
|
||||
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr)
|
||||
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
||||
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
|
||||
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
|
||||
|
||||
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr)
|
||||
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
|
||||
@@ -140,7 +137,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
if(errors.noErrors()) {
|
||||
// check loop range values
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
val range = forLoop.iterable as? RangeExpression
|
||||
if(range!=null) {
|
||||
val from = range.from as? NumericLiteralValue
|
||||
val to = range.to as? NumericLiteralValue
|
||||
@@ -488,22 +485,14 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// target type check is already done at the Assignment:
|
||||
// val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR)
|
||||
// if(targetDt in IterableDatatypes)
|
||||
// errors.err("cannot assign to a string or array type", assignTarget.position)
|
||||
|
||||
if (assignment is Assignment) {
|
||||
|
||||
val targetDatatype = assignTarget.inferType(program)
|
||||
if (targetDatatype.isKnown) {
|
||||
val constVal = assignment.value.constValue(program)
|
||||
if (constVal != null) {
|
||||
checkValueTypeAndRange(targetDatatype.getOr(DataType.BYTE), constVal)
|
||||
} else {
|
||||
if(constVal==null) {
|
||||
val sourceDatatype = assignment.value.inferType(program)
|
||||
if (sourceDatatype.isUnknown) {
|
||||
if (assignment.value !is FunctionCallExpr)
|
||||
if (assignment.value !is FunctionCallExpression)
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
|
||||
@@ -534,14 +523,6 @@ internal class AstChecker(private val program: Program,
|
||||
err("const modifier can only be used on numeric types (byte, word, float)")
|
||||
}
|
||||
|
||||
// @zp can only occur on integers
|
||||
if(decl.datatype !in IntegerDatatypes) {
|
||||
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
||||
errors.warn("this datatype can't be placed in zeropage", decl.position)
|
||||
if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
|
||||
errors.err("this datatype can't be placed in zeropage", decl.position)
|
||||
}
|
||||
|
||||
// FLOATS enabled?
|
||||
if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
|
||||
err("floating point used, but that is not enabled via options")
|
||||
@@ -558,7 +539,7 @@ internal class AstChecker(private val program: Program,
|
||||
err("unsized array declaration cannot use a single literal initialization value")
|
||||
return
|
||||
}
|
||||
if(decl.value is RangeExpr)
|
||||
if(decl.value is RangeExpression)
|
||||
throw FatalAstException("range expressions in vardecls should have been converted into array values during constFolding $decl")
|
||||
}
|
||||
|
||||
@@ -568,7 +549,7 @@ internal class AstChecker(private val program: Program,
|
||||
null -> {
|
||||
// a vardecl without an initial value, don't bother with it
|
||||
}
|
||||
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
|
||||
is RangeExpression -> throw FatalAstException("range expression should have been converted to a true array value")
|
||||
is StringLiteralValue -> {
|
||||
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
|
||||
}
|
||||
@@ -659,6 +640,9 @@ internal class AstChecker(private val program: Program,
|
||||
err("string var can only be initialized with a string literal")
|
||||
}
|
||||
|
||||
if(compilerOptions.zeropage==ZeropageType.DONTUSE && decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
|
||||
err("zeropage usage has been disabled by options")
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
@@ -695,6 +679,8 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=2 || directive.args[0].int==null || directive.args[1].int==null)
|
||||
err("requires two addresses (start, end)")
|
||||
if(directive.args[0].int!! > 255u || directive.args[1].int!! > 255u)
|
||||
err("start and end addresss must be in Zeropage so 0..255")
|
||||
}
|
||||
"%address" -> {
|
||||
if(directive.parent !is Module)
|
||||
@@ -793,7 +779,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(char: CharLiteral) {
|
||||
try { // just *try* if it can be encoded, don't actually do it
|
||||
compilerOptions.compTarget.encodeString(char.value.toString(), char.altEncoding)
|
||||
compilerOptions.compTarget.encodeString(char.value.toString(), char.encoding)
|
||||
} catch (cx: CharConversionException) {
|
||||
errors.err(cx.message ?: "can't encode character", char.position)
|
||||
}
|
||||
@@ -805,7 +791,9 @@ internal class AstChecker(private val program: Program,
|
||||
checkValueTypeAndRangeString(DataType.STR, string)
|
||||
|
||||
try { // just *try* if it can be encoded, don't actually do it
|
||||
compilerOptions.compTarget.encodeString(string.value, string.altEncoding)
|
||||
val bytes = compilerOptions.compTarget.encodeString(string.value, string.encoding)
|
||||
if(0u in bytes)
|
||||
errors.warn("a character in the string encodes into the 0-byte, which will terminate the string prematurely", string.position)
|
||||
} catch (cx: CharConversionException) {
|
||||
errors.err(cx.message ?: "can't encode string", string.position)
|
||||
}
|
||||
@@ -900,10 +888,13 @@ internal class AstChecker(private val program: Program,
|
||||
if(!typecast.expression.inferType(program).isKnown)
|
||||
errors.err("this expression doesn't return a value", typecast.expression.position)
|
||||
|
||||
if(typecast.expression is NumericLiteralValue)
|
||||
errors.err("can't cast the value to the requested target type", typecast.expression.position)
|
||||
|
||||
super.visit(typecast)
|
||||
}
|
||||
|
||||
override fun visit(range: RangeExpr) {
|
||||
override fun visit(range: RangeExpression) {
|
||||
fun err(msg: String) {
|
||||
errors.err(msg, range.position)
|
||||
}
|
||||
@@ -936,7 +927,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) {
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
// this function call is (part of) an expression, which should be in a statement somewhere.
|
||||
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
|
||||
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
|
||||
@@ -1081,8 +1072,8 @@ internal class AstChecker(private val program: Program,
|
||||
var ident: IdentifierReference? = null
|
||||
if(arg.value is IdentifierReference)
|
||||
ident = arg.value as IdentifierReference
|
||||
else if(arg.value is FunctionCallExpr) {
|
||||
val fcall = arg.value as FunctionCallExpr
|
||||
else if(arg.value is FunctionCallExpression) {
|
||||
val fcall = arg.value as FunctionCallExpression
|
||||
if(fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))
|
||||
ident = fcall.args[0] as? IdentifierReference
|
||||
}
|
||||
@@ -1208,16 +1199,112 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(containment: ContainmentCheck) {
|
||||
if(!containment.iterable.inferType(program).isIterable)
|
||||
errors.err("value set for containment check must be an iterable type", containment.iterable.position)
|
||||
val elementDt = containment.element.inferType(program)
|
||||
val iterableDt = containment.iterable.inferType(program)
|
||||
|
||||
if(containment.parent is BinaryExpression)
|
||||
errors.err("containment check is currently not supported in complex expressions", containment.position)
|
||||
|
||||
// TODO check that iterable contains the same types as the element that is searched
|
||||
if(iterableDt.isIterable) {
|
||||
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))
|
||||
val invalidDt = if (elementDt.isBytes) {
|
||||
iterableEltDt !in ByteDatatypes
|
||||
} else if (elementDt.isWords) {
|
||||
iterableEltDt !in WordDatatypes
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if (invalidDt)
|
||||
errors.err("element datatype doesn't match iterable datatype", containment.position)
|
||||
} else {
|
||||
errors.err("value set for containment check must be an iterable type", containment.iterable.position)
|
||||
}
|
||||
|
||||
super.visit(containment)
|
||||
}
|
||||
|
||||
override fun visit(pipe: PipeExpression) {
|
||||
processPipe(pipe.expressions, pipe)
|
||||
val last = pipe.expressions.last() as IdentifierReference
|
||||
val target = last.targetStatement(program)!!
|
||||
when(target) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
if(BuiltinFunctions.getValue(target.name).known_returntype==null)
|
||||
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
|
||||
}
|
||||
is Subroutine -> {
|
||||
if(target.returntypes.isEmpty())
|
||||
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
|
||||
else if(target.returntypes.size!=1)
|
||||
errors.err("invalid pipe expression; last term doesn't return a single value", last.position)
|
||||
}
|
||||
else -> errors.err("invalid pipe expression; last term doesn't return a value", last.position)
|
||||
}
|
||||
super.visit(pipe)
|
||||
}
|
||||
|
||||
override fun visit(pipe: Pipe) {
|
||||
processPipe(pipe.expressions, pipe)
|
||||
super.visit(pipe)
|
||||
}
|
||||
|
||||
private fun processPipe(expressions: List<Expression>, scope: Node) {
|
||||
// first expression is just any expression producing a value
|
||||
// all other expressions should be the name of a unary function that returns a single value
|
||||
// the last expression should be the name of a unary function whose return value we don't care about.
|
||||
if (expressions.size < 2) {
|
||||
errors.err("pipe is missing one or more expressions", scope.position)
|
||||
} else {
|
||||
// invalid size and other issues will be handled by the ast checker later.
|
||||
var valueDt = expressions[0].inferType(program).getOrElse {
|
||||
throw FatalAstException("invalid dt ${expressions[0]} @ ${scope.position}")
|
||||
}
|
||||
|
||||
for(expr in expressions.drop(1)) { // just keep the first expression value as-is
|
||||
val functionName = expr as? IdentifierReference
|
||||
val function = functionName?.targetStatement(program)
|
||||
if(functionName!=null && function!=null) {
|
||||
when (function) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(function.name)
|
||||
if(func.parameters.size!=1)
|
||||
errors.err("can only use unary function", expr.position)
|
||||
else if(func.known_returntype==null && expr !== expressions.last())
|
||||
errors.err("function must return a single value", expr.position)
|
||||
|
||||
val paramDts = func.parameters.firstOrNull()?.possibleDatatypes
|
||||
if(paramDts!=null && !paramDts.any { valueDt isAssignableTo it })
|
||||
errors.err("pipe value datatype $valueDt incompatible withfunction argument ${paramDts.toList()}", functionName.position)
|
||||
|
||||
valueDt = func.known_returntype!!
|
||||
}
|
||||
is Subroutine -> {
|
||||
if(function.parameters.size!=1)
|
||||
errors.err("can only use unary function", expr.position)
|
||||
else if(function.returntypes.size!=1 && expr !== expressions.last())
|
||||
errors.err("function must return a single value", expr.position)
|
||||
|
||||
val paramDt = function.parameters.firstOrNull()?.type
|
||||
if(paramDt!=null && !(valueDt isAssignableTo paramDt))
|
||||
errors.err("pipe value datatype $valueDt incompatible with function argument $paramDt", functionName.position)
|
||||
|
||||
if(function.returntypes.isNotEmpty())
|
||||
valueDt = function.returntypes.single()
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("weird function")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(expr is IFunctionCall)
|
||||
errors.err("use only the name of the function, not a call", expr.position)
|
||||
else
|
||||
errors.err("can only use unary function", expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
|
||||
when (val targetStatement = target.targetStatement(program)) {
|
||||
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
|
||||
@@ -1229,12 +1316,21 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteralValue) : Boolean {
|
||||
return if (targetDt == DataType.STR) {
|
||||
if (value.value.length > 255) {
|
||||
errors.err("string length must be 0-255", value.position)
|
||||
false
|
||||
when {
|
||||
value.value.length > 255 -> {
|
||||
errors.err("string length must be 0-255", value.position)
|
||||
false
|
||||
}
|
||||
value.value.isEmpty() -> {
|
||||
val decl = value.parent as? VarDecl
|
||||
if(decl!=null && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)) {
|
||||
errors.err("string in Zeropage must be non-empty", value.position)
|
||||
false
|
||||
}
|
||||
else true
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
else
|
||||
true
|
||||
}
|
||||
else false
|
||||
}
|
||||
@@ -1403,7 +1499,7 @@ internal class AstChecker(private val program: Program,
|
||||
sourceValue: Expression) : Boolean {
|
||||
val position = sourceValue.position
|
||||
|
||||
if(sourceValue is RangeExpr)
|
||||
if(sourceValue is RangeExpression)
|
||||
errors.err("can't assign a range value to something else", position)
|
||||
|
||||
val result = when(targetDatatype) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import prog8.ast.expressions.CharLiteral
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.VarDeclOrigin
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
@@ -23,11 +23,16 @@ internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: Compila
|
||||
}
|
||||
|
||||
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, compilerOptions, errors)
|
||||
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
|
||||
fixer.visit(this)
|
||||
while(errors.noErrors() && fixer.applyModifications()>0) {
|
||||
fixer.visit(this)
|
||||
}
|
||||
val cleaner = BeforeAsmTypecastCleaner(this, errors)
|
||||
cleaner.visit(this)
|
||||
while(errors.noErrors() && cleaner.applyModifications()>0) {
|
||||
cleaner.visit(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements(errors: IErrorReporter, options: CompilationOptions) {
|
||||
@@ -46,7 +51,7 @@ internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
|
||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
char,
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toDouble(), char.position),
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.encoding)[0].toDouble(), char.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
@@ -96,8 +101,8 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
|
||||
val process = VariousCleanups(program, errors)
|
||||
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter, options: CompilationOptions) {
|
||||
val process = VariousCleanups(program, errors, options)
|
||||
process.visit(this)
|
||||
if(errors.noErrors())
|
||||
process.applyModifications()
|
||||
@@ -135,7 +140,7 @@ internal fun Program.moveMainAndStartToFirst() {
|
||||
|
||||
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
|
||||
val vardecl = this.targetVarDecl(program)
|
||||
if(vardecl!=null && vardecl.autogeneratedDontRemove) {
|
||||
if(vardecl!=null && vardecl.origin==VarDeclOrigin.SUBROUTINEPARAM) {
|
||||
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -4,11 +4,12 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCallExpr
|
||||
import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
@@ -128,7 +129,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) = visitFunctionCall(functionCallExpr)
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) = visitFunctionCall(functionCallExpr)
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
||||
|
||||
private fun visitFunctionCall(call: IFunctionCall) {
|
||||
@@ -154,7 +155,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
else
|
||||
'_'
|
||||
}.joinToString("")
|
||||
call.args[0] = StringLiteralValue(processed, false, name.position)
|
||||
call.args[0] = StringLiteralValue(processed, Encoding.PETSCII, name.position)
|
||||
call.args[0].linkParents(call as Node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
|
||||
// has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(range.from !is NumericLiteralValue) {
|
||||
@@ -49,7 +49,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
|
||||
|
||||
// move vardecls in Anonymous scope up to the containing subroutine
|
||||
// and add initialization assignment in its place if needed
|
||||
val vars = scope.statements.filterIsInstance<VarDecl>()
|
||||
val vars = scope.statements.asSequence().filterIsInstance<VarDecl>()
|
||||
val parentscope = scope.definingScope
|
||||
if(vars.any() && parentscope !== parent) {
|
||||
val movements = mutableListOf<IAstModification>()
|
||||
@@ -62,7 +62,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
|
||||
} else {
|
||||
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, decl.value!!, decl.position)
|
||||
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
@@ -85,4 +85,16 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val nextAssignment = decl.nextSibling() as? Assignment
|
||||
if(nextAssignment!=null && nextAssignment.origin!=AssignmentOrigin.VARINIT) {
|
||||
// check if it's a proper initializer assignment for the variable
|
||||
if(decl.value==null && nextAssignment.target.identifier?.targetVarDecl(program)===decl) {
|
||||
if(!nextAssignment.value.referencesIdentifier(nextAssignment.target.identifier!!.nameInSource))
|
||||
nextAssignment.origin = AssignmentOrigin.VARINIT
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty()) {
|
||||
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
val vars = subroutine.statements.asSequence().filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||
return subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
@@ -49,7 +49,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = leftStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
|
||||
val strval = StringLiteralValue(string, leftStr.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = rightStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
|
||||
val strval = StringLiteralValue(string, rightStr.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
@@ -80,17 +80,17 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position)
|
||||
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.encoding, rightStrval.position)
|
||||
}
|
||||
expr.right is BinaryExpression && leftStrval!=null -> {
|
||||
val subStrVal = concatString(expr.right as BinaryExpression)
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position)
|
||||
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.encoding, leftStrval.position)
|
||||
}
|
||||
leftStrval!=null && rightStrval!=null -> {
|
||||
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position)
|
||||
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.encoding, leftStrval.position)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
+71
-82
@@ -1,4 +1,4 @@
|
||||
package prog8.compiler
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
@@ -10,14 +10,15 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.astprocessing.isSubroutineParameter
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.isIOAddress
|
||||
import prog8.optimizer.getTempVarName
|
||||
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter) : AstWalker() {
|
||||
internal class BeforeAsmAstChanger(val program: Program, private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter
|
||||
) : AstWalker() {
|
||||
|
||||
private val subroutineVariables = mutableMapOf<Subroutine, MutableList<Pair<String, VarDecl>>>()
|
||||
|
||||
@@ -52,7 +53,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
|
||||
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")
|
||||
rememberSubroutineVar(decl)
|
||||
return noModifications
|
||||
@@ -63,8 +64,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
// this triggers the more efficent augmented assignment code generation more often.
|
||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
|
||||
&& assignment.target.identifier != null
|
||||
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
|
||||
if(binExpr!=null && binExpr.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
|
||||
@@ -79,20 +80,28 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
// 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("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)
|
||||
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
|
||||
"unknown dt"
|
||||
)
|
||||
}, sourceDt, implicit=true)
|
||||
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.BEFOREASMGEN, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
|
||||
"unknown dt"
|
||||
)
|
||||
}, sourceDt, implicit=true)
|
||||
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.BEFOREASMGEN, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,9 +135,9 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
if (subroutine.asmAddress == null && !subroutine.inline) {
|
||||
if(subroutine.statements.isEmpty() ||
|
||||
(subroutine.amountOfRtsInAsm() == 0
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine)) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine)) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,73 +147,39 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if (subroutineStmtIdx > 0) {
|
||||
val prevStmt = outerStatements[subroutineStmtIdx-1]
|
||||
if(outerScope !is Block
|
||||
&& (prevStmt !is Jump)
|
||||
&& prevStmt !is Subroutine
|
||||
&& prevStmt !is Return) {
|
||||
if(outerScope !is Block
|
||||
&& (prevStmt !is Jump)
|
||||
&& prevStmt !is Subroutine
|
||||
&& prevStmt !is Return
|
||||
) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove redundant typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword
|
||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
|
||||
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
|
||||
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||
if(typecast.parent !is Expression) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type==DataType.UWORD) {
|
||||
val identifier = typecast.expression as? IdentifierReference
|
||||
if(identifier!=null) {
|
||||
return if(identifier.isSubroutineParameter(program)) {
|
||||
listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
typecast.expression,
|
||||
parent
|
||||
))
|
||||
} else {
|
||||
listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(identifier, typecast.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
} else if(typecast.expression is IFunctionCall) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
typecast.expression,
|
||||
parent
|
||||
))
|
||||
}
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
val prefixExpr = ifElse.condition as? PrefixExpression
|
||||
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||
// if not x -> if x==0
|
||||
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position)
|
||||
val booleanExpr = BinaryExpression(
|
||||
prefixExpr.expression,
|
||||
"==",
|
||||
NumericLiteralValue.optimalInteger(0, ifElse.condition.position),
|
||||
ifElse.condition.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
|
||||
}
|
||||
|
||||
val binExpr = ifElse.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in ComparisonOperators) {
|
||||
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||
val booleanExpr = BinaryExpression(ifElse.condition, "!=", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position)
|
||||
val booleanExpr = BinaryExpression(
|
||||
ifElse.condition,
|
||||
"!=",
|
||||
NumericLiteralValue.optimalInteger(0, ifElse.condition.position),
|
||||
ifElse.condition.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
|
||||
}
|
||||
|
||||
@@ -219,11 +194,19 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(simplify.rightVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(ifElse, simplify.rightVarAssignment, parent as IStatementContainer)
|
||||
modifications += IAstModification.InsertBefore(
|
||||
ifElse,
|
||||
simplify.rightVarAssignment,
|
||||
parent as IStatementContainer
|
||||
)
|
||||
}
|
||||
if(simplify.leftVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(ifElse, simplify.leftVarAssignment, parent as IStatementContainer)
|
||||
modifications += IAstModification.InsertBefore(
|
||||
ifElse,
|
||||
simplify.leftVarAssignment,
|
||||
parent as IStatementContainer
|
||||
)
|
||||
}
|
||||
|
||||
return modifications
|
||||
@@ -240,9 +223,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
|
||||
// NOTE: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
var leftOperandReplacement: Expression? = null
|
||||
@@ -265,7 +248,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
leftAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.left,
|
||||
expr.position
|
||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
if(separateRightExpr) {
|
||||
@@ -280,7 +263,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
rightAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.right,
|
||||
expr.position
|
||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
return CondExprSimplificationResult(
|
||||
@@ -299,13 +282,13 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes)
|
||||
return noModifications
|
||||
val (replaced, cast) = arg1.typecastTo(if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
|
||||
val (replaced, cast) = arg1.typecastTo(if(dt1== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
|
||||
} else {
|
||||
if(dt2 in WordDatatypes)
|
||||
return noModifications
|
||||
val (replaced, cast) = arg2.typecastTo(if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
|
||||
val (replaced, cast) = arg2.typecastTo(if(dt2== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
|
||||
}
|
||||
@@ -373,10 +356,16 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
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)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.BEFOREASMGEN, 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))
|
||||
modifications.add(
|
||||
IAstModification.ReplaceNode(
|
||||
expr.indexer.indexExpr,
|
||||
target.identifier!!.copy(),
|
||||
expr.indexer
|
||||
)
|
||||
)
|
||||
return modifications
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
private val errors: IErrorReporter
|
||||
) : AstWalker() {
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove redundant typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword or even redundant casts (sourcetype = target type).
|
||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
|
||||
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
|
||||
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes
|
||||
) {
|
||||
if(typecast.parent !is Expression) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(typecast.type==sourceDt)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type== DataType.UWORD) {
|
||||
val identifier = typecast.expression as? IdentifierReference
|
||||
if(identifier!=null) {
|
||||
return if(identifier.isSubroutineParameter(program)) {
|
||||
listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
typecast.expression,
|
||||
parent
|
||||
)
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(identifier, typecast.position),
|
||||
parent
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if(typecast.expression is IFunctionCall) {
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
typecast.expression,
|
||||
parent
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ _after:
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node) =
|
||||
before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpr, parent: Node) =
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node) =
|
||||
before(functionCallExpr as IFunctionCall, parent, functionCallExpr.position)
|
||||
|
||||
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
||||
@@ -129,16 +129,9 @@ _after:
|
||||
if(functionCall.target.nameInSource==listOf("poke")) {
|
||||
// poke(a, v) is synonymous with @(a) = v
|
||||
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
|
||||
val assign = Assignment(tgt, functionCall.args[1], position)
|
||||
val assign = Assignment(tgt, functionCall.args[1], AssignmentOrigin.OPTIMIZER, position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator=="in") {
|
||||
println("IN-TEST: $expr\n in: $parent")
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(range: RangeExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(range.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $range")
|
||||
return noModifications
|
||||
@@ -221,7 +221,7 @@ internal class ParentNodeChecker: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallExpr.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $functionCallExpr")
|
||||
return noModifications
|
||||
|
||||
@@ -31,7 +31,7 @@ internal class StatementReorderer(val program: Program,
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: UInt.MAX_VALUE }).toMutableList()
|
||||
|
||||
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||
val mainBlock = module.statements.asSequence().filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||
if(mainBlock!=null && mainBlock.address==null) {
|
||||
module.statements.remove(mainBlock)
|
||||
module.statements.add(0, mainBlock)
|
||||
@@ -48,7 +48,7 @@ internal class StatementReorderer(val program: Program,
|
||||
if(decl !in declsProcessedWithInitAssignment) {
|
||||
declsProcessedWithInitAssignment.add(decl)
|
||||
if (decl.value == null) {
|
||||
if (!decl.autogeneratedDontRemove && decl.allowInitializeWithZero) {
|
||||
if (decl.origin==VarDeclOrigin.USERCODE && decl.allowInitializeWithZero) {
|
||||
// A numeric vardecl without an initial value is initialized with zero,
|
||||
// unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar).
|
||||
// This allows you to restart the program and have the same starting values of the variables
|
||||
@@ -58,22 +58,15 @@ internal class StatementReorderer(val program: Program,
|
||||
// no need to zero out the special internal returnvalue intermediates.
|
||||
return noModifications
|
||||
}
|
||||
val nextStmt = decl.nextSibling()
|
||||
val nextAssign = nextStmt as? Assignment
|
||||
if(nextAssign!=null && !nextAssign.isAugmentable) {
|
||||
val target = nextAssign.target.identifier?.targetStatement(program)
|
||||
if(target === decl) {
|
||||
// an initializer assignment for a vardecl is already here
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
val nextFor = nextStmt as? ForLoop
|
||||
if(decl.findInitializer(program)!=null)
|
||||
return noModifications // an initializer assignment for a vardecl is already here
|
||||
val nextFor = decl.nextSibling() as? ForLoop
|
||||
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
|
||||
if (!hasNextForWithThisLoopvar) {
|
||||
// Add assignment to initialize with zero
|
||||
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
|
||||
val identifier = IdentifierReference(listOf(decl.name), decl.position)
|
||||
val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), decl.position)
|
||||
val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position)
|
||||
return listOf(IAstModification.InsertAfter(
|
||||
decl, assignzero, parent as IStatementContainer
|
||||
))
|
||||
@@ -85,7 +78,7 @@ internal class StatementReorderer(val program: Program,
|
||||
// So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99'
|
||||
val pos = decl.value!!.position
|
||||
val identifier = IdentifierReference(listOf(decl.name), pos)
|
||||
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, pos)
|
||||
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos)
|
||||
decl.value = null
|
||||
return listOf(IAstModification.InsertAfter(
|
||||
decl, assign, parent as IStatementContainer
|
||||
@@ -118,7 +111,7 @@ internal class StatementReorderer(val program: Program,
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
if(subroutine.name=="start" && parent is Block) {
|
||||
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
|
||||
if(parent.statements.asSequence().filterIsInstance<Subroutine>().first().name!="start") {
|
||||
return listOf(
|
||||
IAstModification.Remove(subroutine, parent),
|
||||
IAstModification.InsertFirst(subroutine, parent)
|
||||
@@ -150,14 +143,24 @@ internal class StatementReorderer(val program: Program,
|
||||
varsChanges +=
|
||||
if(stringParamsByNames.isNotEmpty()) {
|
||||
subroutine.statements
|
||||
.asSequence()
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames }
|
||||
.map {
|
||||
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, stringParamsByNames.getValue(it.name), it.position)
|
||||
val newvar = VarDecl(it.type, it.origin, DataType.UWORD,
|
||||
it.zeropage,
|
||||
null,
|
||||
it.name,
|
||||
null,
|
||||
false,
|
||||
it.sharedWithAsm,
|
||||
stringParamsByNames.getValue(it.name),
|
||||
it.position
|
||||
)
|
||||
IAstModification.ReplaceNode(it, newvar, subroutine)
|
||||
}
|
||||
}
|
||||
else emptyList()
|
||||
else emptySequence()
|
||||
}
|
||||
|
||||
return modifications + parameterChanges + varsChanges
|
||||
@@ -382,118 +385,130 @@ internal class StatementReorderer(val program: Program,
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
val function = functionCallStatement.target.targetStatement(program)!!
|
||||
checkUnusedReturnValues(functionCallStatement, function, program, errors)
|
||||
if(function is Subroutine) {
|
||||
if(function.inline)
|
||||
return noModifications
|
||||
return if(function.isAsmSubroutine)
|
||||
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent)
|
||||
else
|
||||
replaceCallSubStatementWithGosub(function, functionCallStatement, parent)
|
||||
}
|
||||
return noModifications
|
||||
return replaceCallByGosub(functionCallStatement, parent, program, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun replaceCallByGosub(functionCallStatement: FunctionCallStatement,
|
||||
parent: Node,
|
||||
program: Program,
|
||||
options: CompilationOptions): Iterable<IAstModification> {
|
||||
val function = functionCallStatement.target.targetStatement(program)!!
|
||||
if(function is Subroutine) {
|
||||
if(function.inline)
|
||||
return emptyList()
|
||||
return if(function.isAsmSubroutine)
|
||||
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent, options)
|
||||
else
|
||||
replaceCallSubStatementWithGosub(function, functionCallStatement, parent, program)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node, program: Program): Iterable<IAstModification> {
|
||||
val noModifications = emptyList<IAstModification>()
|
||||
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
|
||||
}
|
||||
|
||||
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
|
||||
if(function.parameters.size==1) {
|
||||
if(function.parameters[0].type in IntegerDatatypes) {
|
||||
// optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
else if(function.parameters.size==2) {
|
||||
if(function.parameters[0].type in ByteDatatypes && function.parameters[1].type in ByteDatatypes) {
|
||||
// optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
if(function.parameters.size==1) {
|
||||
if(function.parameters[0].type in IntegerDatatypes) {
|
||||
// optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
else if(function.parameters.size==2) {
|
||||
if(function.parameters[0].type in ByteDatatypes && function.parameters[1].type in ByteDatatypes) {
|
||||
// optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables
|
||||
return noModifications
|
||||
val assignParams =
|
||||
function.parameters.zip(call.args).map {
|
||||
var argumentValue = it.second
|
||||
val paramIdentifier = IdentifierReference(function.scopedName + it.first.name, argumentValue.position)
|
||||
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(argDt in ArrayDatatypes) {
|
||||
// pass the address of the array instead
|
||||
if(argumentValue is IdentifierReference)
|
||||
argumentValue = AddressOf(argumentValue, argumentValue.position)
|
||||
}
|
||||
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, AssignmentOrigin.PARAMETERASSIGN, argumentValue.position)
|
||||
}
|
||||
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
|
||||
val assignParams =
|
||||
function.parameters.zip(call.args).map {
|
||||
var argumentValue = it.second
|
||||
val paramIdentifier = IdentifierReference(function.scopedName + it.first.name, argumentValue.position)
|
||||
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(argDt in ArrayDatatypes) {
|
||||
// pass the address of the array instead
|
||||
if(argumentValue is IdentifierReference)
|
||||
argumentValue = AddressOf(argumentValue, argumentValue.position)
|
||||
}
|
||||
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position)
|
||||
}
|
||||
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
|
||||
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node, options: CompilationOptions): Iterable<IAstModification> {
|
||||
val noModifications = emptyList<IAstModification>()
|
||||
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
} else if(!options.compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, function.asmParameterRegisters)) {
|
||||
// No register clobber risk, let the asmgen assign values to the registers directly.
|
||||
// this is more efficient than first evaluating them to the stack.
|
||||
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
|
||||
return noModifications
|
||||
} else {
|
||||
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
|
||||
val argOrder = options.compTarget.asmsubArgsEvalOrder(function)
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
argOrder.reversed().forEach {
|
||||
val arg = call.args[it]
|
||||
val param = function.parameters[it]
|
||||
scope.statements += pushCall(arg, param.type, arg.position)
|
||||
}
|
||||
// ... and pop them off again into the registers.
|
||||
argOrder.forEach {
|
||||
val param = function.parameters[it]
|
||||
val targetName = function.scopedName + param.name
|
||||
scope.statements += popCall(targetName, param.type, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
} else if(!options.compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, function.asmParameterRegisters)) {
|
||||
// No register clobber risk, let the asmgen assign values to the registers directly.
|
||||
// this is more efficient than first evaluating them to the stack.
|
||||
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
|
||||
return noModifications
|
||||
} else {
|
||||
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
|
||||
val argOrder = options.compTarget.asmsubArgsEvalOrder(function)
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
argOrder.reversed().forEach {
|
||||
val arg = call.args[it]
|
||||
val param = function.parameters[it]
|
||||
scope.statements += pushCall(arg, param.type, arg.position)
|
||||
}
|
||||
// ... and pop them off again into the registers.
|
||||
argOrder.forEach {
|
||||
val param = function.parameters[it]
|
||||
val targetName = function.scopedName + param.name
|
||||
scope.statements += popCall(targetName, param.type, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
}
|
||||
|
||||
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
|
||||
mutableListOf(IdentifierReference(targetName, position)),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
|
||||
val pushvalue = when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> value
|
||||
in PassByReferenceDatatypes -> value
|
||||
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
|
||||
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
|
||||
else -> throw FatalAstException("invalid dt $dt $value")
|
||||
}
|
||||
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
|
||||
mutableListOf(IdentifierReference(targetName, position)),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
|
||||
mutableListOf(pushvalue),
|
||||
true, position
|
||||
)
|
||||
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
|
||||
val pushvalue = when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> value
|
||||
in PassByReferenceDatatypes -> value
|
||||
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
|
||||
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
|
||||
else -> throw FatalAstException("invalid dt $dt $value")
|
||||
}
|
||||
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
|
||||
mutableListOf(pushvalue),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
@@ -33,11 +33,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if(valueDt isNotAssignableTo decl.datatype)
|
||||
return noModifications
|
||||
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
declValue,
|
||||
TypecastExpression(declValue, decl.datatype, true, declValue.position),
|
||||
decl
|
||||
))
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
addTypecastOrCastedValueModification(modifications, declValue, decl.datatype, decl)
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@@ -66,18 +64,40 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
NumericLiteralValue(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
|
||||
expr))
|
||||
}
|
||||
|
||||
if(leftDt istype DataType.BYTE && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast left to unsigned
|
||||
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
if(leftDt istype DataType.WORD && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast left to unsigned
|
||||
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
if(rightDt istype DataType.BYTE && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast right to unsigned
|
||||
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
|
||||
}
|
||||
if(rightDt istype DataType.WORD && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
|
||||
// cast right to unsigned
|
||||
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.operator, expr.right)
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
|
||||
if(toFix!=null) {
|
||||
return when {
|
||||
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
|
||||
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
|
||||
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
when {
|
||||
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr)
|
||||
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr)
|
||||
else -> throw FatalAstException("confused binary expression side")
|
||||
}
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@@ -95,10 +115,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
||||
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
|
||||
return noModifications
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
addTypecastOrCastedValueModification(modifications, assignment.value, targettype, assignment)
|
||||
return modifications
|
||||
} else {
|
||||
fun castLiteral(cvalue2: NumericLiteralValue): List<IAstModification.ReplaceNode> {
|
||||
val cast = cvalue2.cast(targettype)
|
||||
@@ -135,7 +154,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
return afterFunctionCallArgs(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
return afterFunctionCallArgs(functionCallExpr)
|
||||
}
|
||||
|
||||
@@ -154,17 +173,14 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
// 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)
|
||||
addTypecastOrCastedValueModification(modifications, pair.second, requiredType, 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
|
||||
val identifier = pair.second as? IdentifierReference
|
||||
if(identifier?.isSubroutineParameter(program)==false) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
identifier,
|
||||
AddressOf(identifier, pair.second.position),
|
||||
call as Node)
|
||||
}
|
||||
@@ -172,7 +188,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
val cast = (pair.second as NumericLiteralValue).cast(requiredType)
|
||||
if(cast.isValid)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
pair.second,
|
||||
cast.valueOrZero(),
|
||||
call as Node)
|
||||
}
|
||||
@@ -189,10 +205,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||
for (possibleType in pair.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
TypecastExpression(pair.second, possibleType, true, pair.second.position),
|
||||
call as Node)
|
||||
addTypecastOrCastedValueModification(modifications, pair.second, possibleType, call as Node)
|
||||
break
|
||||
}
|
||||
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
|
||||
@@ -226,29 +239,36 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
else
|
||||
errors.err("integer implicitly converted to float but floating point is not enabled via options", typecast.position)
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val dt = memread.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||
val castedValue = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
if(castedValue!=null)
|
||||
modifications += IAstModification.ReplaceNode(memread.addressExpression, castedValue, memread)
|
||||
else
|
||||
addTypecastOrCastedValueModification(modifications, memread.addressExpression, DataType.UWORD, memread)
|
||||
}
|
||||
return noModifications
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val dt = memwrite.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||
val castedValue = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
if(castedValue!=null)
|
||||
modifications += IAstModification.ReplaceNode(memwrite.addressExpression, castedValue, memwrite)
|
||||
else
|
||||
addTypecastOrCastedValueModification(modifications, memwrite.addressExpression, DataType.UWORD, memwrite)
|
||||
}
|
||||
return noModifications
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
@@ -265,13 +285,32 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if(cast.isValid)
|
||||
returnStmt.value = cast.valueOrZero()
|
||||
} else {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
returnValue,
|
||||
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
|
||||
returnStmt))
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
addTypecastOrCastedValueModification(modifications, returnValue, subReturnType, returnStmt)
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun addTypecastOrCastedValueModification(
|
||||
modifications: MutableList<IAstModification>,
|
||||
expressionToCast: Expression,
|
||||
requiredType: DataType,
|
||||
parent: Node
|
||||
) {
|
||||
val sourceDt = expressionToCast.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(sourceDt == requiredType)
|
||||
return
|
||||
if(expressionToCast is NumericLiteralValue && expressionToCast.type!=DataType.FLOAT) { // refuse to automatically truncate floats
|
||||
val castedValue = expressionToCast.cast(requiredType)
|
||||
if (castedValue.isValid) {
|
||||
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
|
||||
return
|
||||
}
|
||||
}
|
||||
val cast = TypecastExpression(expressionToCast, requiredType, true, expressionToCast.position)
|
||||
modifications += IAstModification.ReplaceNode(expressionToCast, cast, parent)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
@@ -10,10 +11,11 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
|
||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter, val options: CompilationOptions): AstWalker() {
|
||||
|
||||
override fun before(nop: Nop, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nop, parent as IStatementContainer))
|
||||
@@ -62,7 +64,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val nextAssign = assignment.nextSibling() as? Assignment
|
||||
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) {
|
||||
if(nextAssign.value isSameAs assignment.value)
|
||||
if(nextAssign.value isSameAs assignment.value && assignment.value !is IFunctionCall) // don't remove function calls even when they're duplicates
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
@@ -156,7 +158,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
if(stringVal.value.isEmpty())
|
||||
return replaceWithFalse()
|
||||
if(stringVal.value.length==1) {
|
||||
val string = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
|
||||
val string = program.encoding.encodeString(stringVal.value, stringVal.encoding)
|
||||
return replaceWithEquals(NumericLiteralValue(DataType.UBYTE, string[0].toDouble(), stringVal.position))
|
||||
}
|
||||
return noModifications
|
||||
@@ -181,8 +183,8 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
is RangeExpr -> {
|
||||
val constValues = (containment.iterable as RangeExpr).toConstantIntegerRange()
|
||||
is RangeExpression -> {
|
||||
val constValues = (containment.iterable as RangeExpression).toConstantIntegerRange()
|
||||
if(constValues!=null) {
|
||||
if (constValues.isEmpty())
|
||||
return replaceWithFalse()
|
||||
@@ -198,5 +200,9 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
return replaceCallByGosub(functionCallStatement, parent, program, options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCallExpr
|
||||
import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
@@ -13,7 +13,7 @@ import prog8.compilerinterface.InternalCompilerException
|
||||
|
||||
internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) {
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
val error = checkTypes(functionCallExpr as IFunctionCall, program)
|
||||
if(error!=null)
|
||||
throw InternalCompilerException(error)
|
||||
|
||||
@@ -10,8 +10,8 @@ import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
@@ -43,7 +43,7 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
}
|
||||
val arg = funCall.args[0] as NumericLiteralValue
|
||||
arg.type shouldBe DataType.UBYTE
|
||||
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
arg.number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0].toDouble()
|
||||
}
|
||||
|
||||
test("testCharVarAsRomsubArg") {
|
||||
@@ -76,14 +76,14 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
withClue("initializer value should have been moved to separate assignment"){
|
||||
decl.value shouldBe null
|
||||
}
|
||||
val assignInitialValue = decl.nextSibling() as Assignment
|
||||
val assignInitialValue = decl.findInitializer(program)!!
|
||||
assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch")
|
||||
withClue("char literal should have been replaced by ubyte literal") {
|
||||
assignInitialValue.value shouldBe instanceOf<NumericLiteralValue>()
|
||||
}
|
||||
val initializerValue = assignInitialValue.value as NumericLiteralValue
|
||||
initializerValue.type shouldBe DataType.UBYTE
|
||||
initializerValue.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
initializerValue.number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0].toDouble()
|
||||
}
|
||||
|
||||
test("testCharConstAsRomsubArg") {
|
||||
@@ -108,10 +108,10 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
val decl = arg.targetVarDecl(program)!!
|
||||
decl.type shouldBe VarDeclType.CONST
|
||||
decl.datatype shouldBe DataType.UBYTE
|
||||
(decl.value as NumericLiteralValue).number shouldBe platform.encodeString("\n", false)[0]
|
||||
(decl.value as NumericLiteralValue).number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0]
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
arg.number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0].toDouble()
|
||||
}
|
||||
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompila
|
||||
if (target == Cx16Target) {
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn
|
||||
val filepath = searchIn.asSequence()
|
||||
.map { it.resolve("$source.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { workingDir.relativize(it) }
|
||||
|
||||
@@ -94,7 +94,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
)
|
||||
|
||||
tests.forEach {
|
||||
val (where, p8Str, binStr) = it
|
||||
val (where, p8Str, _) = it
|
||||
test("%asmbinary from ${where}folder") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
// val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
|
||||
@@ -12,6 +12,7 @@ import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
@@ -44,8 +45,8 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val rhsValues = (decl.value as ArrayLiteralValue)
|
||||
.value // Array<Expression>
|
||||
.map { (it as NumericLiteralValue).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", true)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", false)[0].toInt()
|
||||
val expectedStart = platform.encodeString("a", Encoding.SCREENCODES)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", Encoding.PETSCII)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
@@ -60,7 +61,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
test("testFloatArrayInitializerWithRange_char_to_char") {
|
||||
val platform = C64Target
|
||||
val result = compileText(platform, optimize = false, """
|
||||
%option enable_floats
|
||||
%import floats
|
||||
main {
|
||||
sub start() {
|
||||
float[] cs = 'a' to 'z' ; values are computed at compile time
|
||||
@@ -76,8 +77,8 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val rhsValues = (decl.value as ArrayLiteralValue)
|
||||
.value // Array<Expression>
|
||||
.map { (it as NumericLiteralValue).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", false)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", false)[0].toInt()
|
||||
val expectedStart = platform.encodeString("a", Encoding.PETSCII)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", Encoding.PETSCII)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
@@ -92,9 +93,9 @@ class TestCompilerOnRanges: FunSpec({
|
||||
context("floatArrayInitializerWithRange") {
|
||||
val combos = cartesianProduct(
|
||||
listOf("", "42", "41"), // sizeInDecl
|
||||
listOf("%option enable_floats", ""), // optEnableFloats
|
||||
listOf("%import floats", ""), // optEnableFloats
|
||||
listOf(Cx16Target, C64Target), // platform
|
||||
listOf(false, true) // optimize
|
||||
listOf(false, true) // optimize
|
||||
)
|
||||
|
||||
combos.forEach {
|
||||
@@ -145,10 +146,10 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val iterable = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }[0]
|
||||
val rangeExpr = iterable as RangeExpr
|
||||
val rangeExpr = iterable as RangeExpression
|
||||
|
||||
val expectedStart = platform.encodeString("a", true)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("f", false)[0].toInt()
|
||||
val expectedStart = platform.encodeString("a", Encoding.SCREENCODES)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("f", Encoding.PETSCII)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
@@ -179,7 +180,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
.filterIsInstance<RangeExpression>()[0]
|
||||
|
||||
rangeExpr.size() shouldBe 2
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
@@ -205,7 +206,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
.filterIsInstance<RangeExpression>()[0]
|
||||
|
||||
rangeExpr.size() shouldBe 9
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
@@ -253,7 +254,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
|
||||
test("testRangeExprNumericSize") {
|
||||
val expr = RangeExpr(
|
||||
val expr = RangeExpression(
|
||||
NumericLiteralValue.optimalInteger(10, Position.DUMMY),
|
||||
NumericLiteralValue.optimalInteger(20, Position.DUMMY),
|
||||
NumericLiteralValue.optimalInteger(2, Position.DUMMY),
|
||||
@@ -278,8 +279,8 @@ class TestCompilerOnRanges: FunSpec({
|
||||
array shouldBe instanceOf<ArrayLiteralValue>()
|
||||
(array as ArrayLiteralValue).value.size shouldBe 26
|
||||
val forloop = (statements.dropLast(1).last() as ForLoop)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpr>()
|
||||
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpression>()
|
||||
(forloop.iterable as RangeExpression).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("range with start/end variables should be ok") {
|
||||
@@ -296,8 +297,8 @@ class TestCompilerOnRanges: FunSpec({
|
||||
""").assertSuccess()
|
||||
val statements = result.program.entrypoint.statements
|
||||
val forloop = (statements.dropLast(1).last() as ForLoop)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpr>()
|
||||
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpression>()
|
||||
(forloop.iterable as RangeExpression).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
|
||||
|
||||
+29
-29
@@ -37,49 +37,49 @@ class TestMemory: FunSpec({
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0002, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xeeee, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
@@ -88,34 +88,34 @@ class TestMemory: FunSpec({
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x0001, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xdfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
|
||||
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(decl, assignment))
|
||||
return target
|
||||
}
|
||||
@@ -138,22 +138,22 @@ class TestMemory: FunSpec({
|
||||
test("memory expression mapped to IO memory on C64") {
|
||||
var memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0xd020, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
printProgram(target.definingModule.program)
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
test("regular variable not in mapped IO ram on C64") {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
@@ -163,9 +163,9 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("memory mapped variable not in mapped IO ram on C64") {
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
@@ -175,9 +175,9 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("memory mapped variable in mapped IO ram on C64") {
|
||||
val address = 0xd020u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
@@ -186,10 +186,10 @@ class TestMemory: FunSpec({
|
||||
}
|
||||
|
||||
test("array not in mapped IO ram") {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, null, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
@@ -199,10 +199,10 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("memory mapped array not in mapped IO ram") {
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
@@ -212,10 +212,10 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("memory mapped array in mapped IO ram") {
|
||||
val address = 0xd800u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
@@ -225,7 +225,7 @@ class TestMemory: FunSpec({
|
||||
|
||||
|
||||
test("memory() with spaces in name works") {
|
||||
val result = compileText(C64Target, false, """
|
||||
compileText(C64Target, false, """
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared mem = memory("a b c", 100)
|
||||
|
||||
@@ -12,6 +12,7 @@ import prog8.ast.expressions.ArrayLiteralValue
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.compilerinterface.Encoding
|
||||
|
||||
class TestNumericLiteralValue: FunSpec({
|
||||
|
||||
@@ -36,13 +37,13 @@ class TestNumericLiteralValue: FunSpec({
|
||||
test("test rounding") {
|
||||
shouldThrow<ExpressionError> {
|
||||
NumericLiteralValue(DataType.BYTE, -2.345, dummyPos)
|
||||
}.message shouldContain "refused silent rounding"
|
||||
}.message shouldContain "refused rounding"
|
||||
shouldThrow<ExpressionError> {
|
||||
NumericLiteralValue(DataType.BYTE, -2.6, dummyPos)
|
||||
}.message shouldContain "refused silent rounding"
|
||||
}.message shouldContain "refused rounding"
|
||||
shouldThrow<ExpressionError> {
|
||||
NumericLiteralValue(DataType.UWORD, 2222.345, dummyPos)
|
||||
}.message shouldContain "refused silent rounding"
|
||||
}.message shouldContain "refused rounding"
|
||||
NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos).number shouldBe 2.0
|
||||
NumericLiteralValue(DataType.BYTE, -2.0, dummyPos).number shouldBe -2.0
|
||||
NumericLiteralValue(DataType.UWORD, 2222.0, dummyPos).number shouldBe 2222.0
|
||||
@@ -94,11 +95,11 @@ class TestNumericLiteralValue: FunSpec({
|
||||
}
|
||||
|
||||
test("testEqualsRef") {
|
||||
(StringLiteralValue("hello", false, dummyPos) == StringLiteralValue("hello", false, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", false, dummyPos) != StringLiteralValue("bye", false, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", true, dummyPos) == StringLiteralValue("hello", true, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("bye", true, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("hello", false, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", Encoding.PETSCII, dummyPos) == StringLiteralValue("hello", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", Encoding.PETSCII, dummyPos) != StringLiteralValue("bye", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", Encoding.SCREENCODES, dummyPos) == StringLiteralValue("hello", Encoding.SCREENCODES, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", Encoding.SCREENCODES, dummyPos) != StringLiteralValue("bye", Encoding.SCREENCODES, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", Encoding.SCREENCODES, dummyPos) != StringLiteralValue("hello", Encoding.PETSCII, dummyPos)) shouldBe true
|
||||
|
||||
val lvOne = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
|
||||
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
|
||||
|
||||
@@ -14,18 +14,14 @@ import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.compiler.astprocessing.processAstBeforeAsmGeneration
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.LauncherType
|
||||
import prog8.compilerinterface.OutputType
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import prog8tests.helpers.generateAssembly
|
||||
|
||||
class TestOptimization: FunSpec({
|
||||
test("remove empty subroutine except start") {
|
||||
@@ -304,15 +300,7 @@ class TestOptimization: FunSpec({
|
||||
expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target)
|
||||
val changer = BeforeAsmGenerationAstChanger(result.program,
|
||||
options,
|
||||
ErrorReporterForTests()
|
||||
)
|
||||
|
||||
changer.visit(result.program)
|
||||
while(changer.applyModifications()>0) {
|
||||
changer.visit(result.program)
|
||||
}
|
||||
result.program.processAstBeforeAsmGeneration(options, ErrorReporterForTests())
|
||||
|
||||
// assignment is now split into:
|
||||
// bb = not bb
|
||||
@@ -403,25 +391,20 @@ class TestOptimization: FunSpec({
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte unused
|
||||
ubyte unused ; removed
|
||||
ubyte @shared unused_but_shared ; this one should remain
|
||||
ubyte usedvar_only_written
|
||||
ubyte usedvar_only_written ; not removed because has multiple assignments
|
||||
usedvar_only_written=2
|
||||
usedvar_only_written++
|
||||
ubyte usedvar ; and this one too
|
||||
ubyte usedvar ; and this one remains too
|
||||
usedvar = msb(usedvar)
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
result.program.entrypoint.statements.size shouldBe 4 // unused_but_shared decl, unused_but_shared=0, usedvar decl, usedvar assign
|
||||
val (decl, assign, decl2, assign2) = result.program.entrypoint.statements
|
||||
decl shouldBe instanceOf<VarDecl>()
|
||||
(decl as VarDecl).name shouldBe "unused_but_shared"
|
||||
assign shouldBe instanceOf<Assignment>()
|
||||
decl2 shouldBe instanceOf<VarDecl>()
|
||||
(decl2 as VarDecl).name shouldBe "usedvar"
|
||||
assign2 shouldBe instanceOf<Assignment>()
|
||||
result.program.entrypoint.statements.size shouldBe 7
|
||||
val alldecls = result.program.entrypoint.allDefinedSymbols.toList()
|
||||
alldecls.map { it.first } shouldBe listOf("unused_but_shared", "usedvar_only_written", "usedvar")
|
||||
}
|
||||
|
||||
test("unused variable removal from subscope") {
|
||||
@@ -429,20 +412,20 @@ class TestOptimization: FunSpec({
|
||||
main {
|
||||
sub start() {
|
||||
if cx16.r0 {
|
||||
uword xx = 42 ; to be removed
|
||||
xx=99 ; to be removed
|
||||
uword xx ; to be removed
|
||||
cx16.r0 = 0
|
||||
}
|
||||
func2()
|
||||
|
||||
sub func2() {
|
||||
uword yy = 33 ; to be removed
|
||||
uword yy ; to be removed
|
||||
yy=99 ; to be removed
|
||||
cx16.r0 = 0
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
printProgram(result.program)
|
||||
result.program.entrypoint.statements.size shouldBe 3
|
||||
val ifstmt = result.program.entrypoint.statements[0] as IfElse
|
||||
ifstmt.truepart.statements.size shouldBe 1
|
||||
@@ -590,9 +573,8 @@ class TestOptimization: FunSpec({
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, optimize=true, src, writeAssembly=false, errors = errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "type of value BYTE doesn't match target UBYTE"
|
||||
errors.errors[1] shouldContain "value '-1' out of range for unsigned byte"
|
||||
}
|
||||
|
||||
test("test augmented expression asmgen") {
|
||||
@@ -622,24 +604,26 @@ class TestOptimization: FunSpec({
|
||||
uword @shared zz
|
||||
zz += 60 ; NOT ok to remove initializer, should evaluate to 60
|
||||
ubyte @shared xx
|
||||
xx = 6+sin8u(xx) ; NOT ok to remove initializer
|
||||
xx = 6+sin8u(xx) ; is not an initializer because it references xx
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
printProgram(result.program)
|
||||
/* expected result:
|
||||
uword yy
|
||||
yy = 20
|
||||
uword zz
|
||||
zz = 60
|
||||
ubyte xx
|
||||
xx = 0
|
||||
xx = sin8u(xx)
|
||||
xx += 6
|
||||
*/
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 7
|
||||
stmts.size shouldBe 8
|
||||
stmts.filterIsInstance<VarDecl>().size shouldBe 3
|
||||
stmts.filterIsInstance<Assignment>().size shouldBe 4
|
||||
stmts.filterIsInstance<Assignment>().size shouldBe 5
|
||||
}
|
||||
|
||||
test("only substitue assignments with 0 after a =0 initializer if it is the same variable") {
|
||||
|
||||
+165
-149
@@ -6,170 +6,186 @@ import com.github.michaelbull.result.getOrElse
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cbm.IsoEncoding
|
||||
import prog8.codegen.target.cbm.PetsciiEncoding
|
||||
|
||||
|
||||
class TestPetscii: FunSpec({
|
||||
class TestStringEncodings: FunSpec({
|
||||
|
||||
test("testZero") {
|
||||
Petscii.encodePetscii("\u0000", true) shouldBe Ok(listOf<UByte>(0u))
|
||||
Petscii.encodePetscii("\u0000", false) shouldBe Ok(listOf<UByte>(0u))
|
||||
Petscii.decodePetscii(listOf(0u), true) shouldBe "\u0000"
|
||||
Petscii.decodePetscii(listOf(0u), false) shouldBe "\u0000"
|
||||
}
|
||||
|
||||
test("testLowercase") {
|
||||
Petscii.encodePetscii("hello WORLD 123 @!£", true) shouldBe
|
||||
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 0xd7u, 0xcfu, 0xd2u, 0xccu, 0xc4u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||
Petscii.encodePetscii("\uf11a", true) shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||
Petscii.encodePetscii("✓", true) shouldBe Ok(listOf<UByte>(0xfau))
|
||||
withClue("expect lowercase error fallback") {
|
||||
Petscii.encodePetscii("π", true) shouldBe Ok(listOf<UByte>(255u))
|
||||
Petscii.encodePetscii("♥", true) shouldBe Ok(listOf<UByte>(0xd3u))
|
||||
context("petscii") {
|
||||
test("testZero") {
|
||||
PetsciiEncoding.encodePetscii("\u0000", true) shouldBe Ok(listOf<UByte>(0u))
|
||||
PetsciiEncoding.encodePetscii("\u0000", false) shouldBe Ok(listOf<UByte>(0u))
|
||||
PetsciiEncoding.decodePetscii(listOf(0u), true) shouldBe Ok("\u0000")
|
||||
PetsciiEncoding.decodePetscii(listOf(0u), false) shouldBe Ok("\u0000")
|
||||
}
|
||||
|
||||
Petscii.decodePetscii(listOf(72u, 0xd7u, 0x5cu, 0xfau, 0x12u), true) shouldBe "hW£✓\uF11A"
|
||||
}
|
||||
test("testLowercase") {
|
||||
PetsciiEncoding.encodePetscii("hello WORLD 123 @!£", true) shouldBe
|
||||
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 0xd7u, 0xcfu, 0xd2u, 0xccu, 0xc4u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||
PetsciiEncoding.encodePetscii("\uf11a", true) shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||
PetsciiEncoding.encodePetscii("✓", true) shouldBe Ok(listOf<UByte>(0xfau))
|
||||
withClue("expect lowercase error fallback") {
|
||||
PetsciiEncoding.encodePetscii("π", true) shouldBe Ok(listOf<UByte>(255u))
|
||||
PetsciiEncoding.encodePetscii("♥", true) shouldBe Ok(listOf<UByte>(0xd3u))
|
||||
}
|
||||
|
||||
test("testUppercase") {
|
||||
Petscii.encodePetscii("HELLO 123 @!£") shouldBe
|
||||
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||
Petscii.encodePetscii("\uf11a") shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||
Petscii.encodePetscii("♥") shouldBe Ok(listOf<UByte>(0xd3u))
|
||||
Petscii.encodePetscii("π") shouldBe Ok(listOf<UByte>(0xffu))
|
||||
withClue("expecting fallback") {
|
||||
Petscii.encodePetscii("✓") shouldBe Ok(listOf<UByte>(250u))
|
||||
PetsciiEncoding.decodePetscii(listOf(72u, 0xd7u, 0x5cu, 0xfau, 0x12u), true) shouldBe Ok("hW£✓\uF11A")
|
||||
}
|
||||
|
||||
Petscii.decodePetscii(listOf(72u, 0x5cu, 0xd3u, 0xffu)) shouldBe "H£♥π"
|
||||
}
|
||||
test("testUppercase") {
|
||||
PetsciiEncoding.encodePetscii("HELLO 123 @!£") shouldBe
|
||||
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||
PetsciiEncoding.encodePetscii("\uf11a") shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||
PetsciiEncoding.encodePetscii("♥") shouldBe Ok(listOf<UByte>(0xd3u))
|
||||
PetsciiEncoding.encodePetscii("π") shouldBe Ok(listOf<UByte>(0xffu))
|
||||
withClue("expecting fallback") {
|
||||
PetsciiEncoding.encodePetscii("✓") shouldBe Ok(listOf<UByte>(250u))
|
||||
}
|
||||
|
||||
test("testScreencodeLowercase") {
|
||||
Petscii.encodeScreencode("hello WORLD 123 @!£", true) shouldBe
|
||||
Ok(listOf<UByte>(0x08u, 0x05u, 0x0cu, 0x0cu, 0x0fu, 0x20u, 0x57u, 0x4fu, 0x52u, 0x4cu, 0x44u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||
Petscii.encodeScreencode("✓", true) shouldBe Ok(listOf<UByte>(0x7au))
|
||||
withClue("expect fallback") {
|
||||
Petscii.encodeScreencode("♥", true) shouldBe Ok(listOf<UByte>(83u))
|
||||
Petscii.encodeScreencode("π", true) shouldBe Ok(listOf<UByte>(94u))
|
||||
PetsciiEncoding.decodePetscii(listOf(72u, 0x5cu, 0xd3u, 0xffu)) shouldBe Ok("H£♥π")
|
||||
}
|
||||
|
||||
Petscii.decodeScreencode(listOf(0x08u, 0x57u, 0x1cu, 0x7au), true) shouldBe "hW£✓"
|
||||
}
|
||||
test("testScreencodeLowercase") {
|
||||
PetsciiEncoding.encodeScreencode("hello WORLD 123 @!£", true) shouldBe
|
||||
Ok(listOf<UByte>(0x08u, 0x05u, 0x0cu, 0x0cu, 0x0fu, 0x20u, 0x57u, 0x4fu, 0x52u, 0x4cu, 0x44u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||
PetsciiEncoding.encodeScreencode("✓", true) shouldBe Ok(listOf<UByte>(0x7au))
|
||||
withClue("expect fallback") {
|
||||
PetsciiEncoding.encodeScreencode("♥", true) shouldBe Ok(listOf<UByte>(83u))
|
||||
PetsciiEncoding.encodeScreencode("π", true) shouldBe Ok(listOf<UByte>(94u))
|
||||
}
|
||||
|
||||
test("testScreencodeUppercase") {
|
||||
Petscii.encodeScreencode("WORLD 123 @!£") shouldBe
|
||||
Ok(listOf<UByte>(0x17u, 0x0fu, 0x12u, 0x0cu, 0x04u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||
Petscii.encodeScreencode("♥") shouldBe Ok(listOf<UByte>(0x53u))
|
||||
Petscii.encodeScreencode("π") shouldBe Ok(listOf<UByte>(0x5eu))
|
||||
Petscii.encodeScreencode("HELLO") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||
withClue("expecting fallback") {
|
||||
Petscii.encodeScreencode("hello") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||
Petscii.encodeScreencode("✓") shouldBe Ok(listOf<UByte>(122u))
|
||||
PetsciiEncoding.decodeScreencode(listOf(0x08u, 0x57u, 0x1cu, 0x7au), true) shouldBe Ok("hW£✓")
|
||||
}
|
||||
|
||||
Petscii.decodeScreencode(listOf(0x17u, 0x1cu, 0x53u, 0x5eu)) shouldBe "W£♥π"
|
||||
test("testScreencodeUppercase") {
|
||||
PetsciiEncoding.encodeScreencode("WORLD 123 @!£") shouldBe
|
||||
Ok(listOf<UByte>(0x17u, 0x0fu, 0x12u, 0x0cu, 0x04u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||
PetsciiEncoding.encodeScreencode("♥") shouldBe Ok(listOf<UByte>(0x53u))
|
||||
PetsciiEncoding.encodeScreencode("π") shouldBe Ok(listOf<UByte>(0x5eu))
|
||||
PetsciiEncoding.encodeScreencode("HELLO") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||
withClue("expecting fallback") {
|
||||
PetsciiEncoding.encodeScreencode("hello") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||
PetsciiEncoding.encodeScreencode("✓") shouldBe Ok(listOf<UByte>(122u))
|
||||
}
|
||||
|
||||
PetsciiEncoding.decodeScreencode(listOf(0x17u, 0x1cu, 0x53u, 0x5eu)) shouldBe Ok("W£♥π")
|
||||
}
|
||||
|
||||
test("testErrorCases") {
|
||||
PetsciiEncoding.encodePetscii("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||
PetsciiEncoding.encodePetscii("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||
PetsciiEncoding.encodeScreencode("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||
PetsciiEncoding.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||
}
|
||||
|
||||
test("testSpecialReplacements") {
|
||||
fun encodeP(c: Char, lower: Boolean) = PetsciiEncoding.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
|
||||
fun encodeS(c: Char, lower: Boolean) = PetsciiEncoding.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
|
||||
|
||||
PetsciiEncoding.encodePetscii("`", false).expectError { "shouldn't have translation for backtick" }
|
||||
PetsciiEncoding.encodePetscii("`", true).expectError { "shouldn't have translation for backtick" }
|
||||
PetsciiEncoding.encodePetscii("~", false).expectError { "shouldn't have translation for tilde" }
|
||||
PetsciiEncoding.encodePetscii("~", true).expectError { "shouldn't have translation for tilde" }
|
||||
|
||||
encodeP('^', false) shouldBe 94u
|
||||
encodeP('^', true) shouldBe 94u
|
||||
encodeS('^', false) shouldBe 30u
|
||||
encodeS('^', true) shouldBe 30u
|
||||
encodeP('_', false) shouldBe 228u
|
||||
encodeP('_', true) shouldBe 228u
|
||||
encodeS('_', false) shouldBe 100u
|
||||
encodeS('_', true) shouldBe 100u
|
||||
encodeP('{', false) shouldBe 243u
|
||||
encodeP('{', true) shouldBe 243u
|
||||
encodeS('{', false) shouldBe 115u
|
||||
encodeS('{', true) shouldBe 115u
|
||||
encodeP('}', false) shouldBe 235u
|
||||
encodeP('}', true) shouldBe 235u
|
||||
encodeS('}', false) shouldBe 107u
|
||||
encodeS('}', true) shouldBe 107u
|
||||
encodeP('|', false) shouldBe 221u
|
||||
encodeP('|', true) shouldBe 221u
|
||||
encodeS('|', false) shouldBe 93u
|
||||
encodeS('|', true) shouldBe 93u
|
||||
encodeP('\\', false) shouldBe 205u
|
||||
encodeP('\\', true) shouldBe 205u
|
||||
encodeS('\\', false) shouldBe 77u
|
||||
encodeS('\\', true) shouldBe 77u
|
||||
}
|
||||
|
||||
test("testBoxDrawingCharsEncoding") {
|
||||
fun encodeP(c: Char, lower: Boolean) = PetsciiEncoding.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
|
||||
fun encodeS(c: Char, lower: Boolean) = PetsciiEncoding.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
|
||||
|
||||
// pipe char
|
||||
encodeP('|', false) shouldBe 221u
|
||||
encodeP('|', true) shouldBe 221u
|
||||
encodeS('|', false) shouldBe 93u
|
||||
encodeS('|', true) shouldBe 93u
|
||||
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
|
||||
encodeP('│', false) shouldBe 221u
|
||||
encodeP('│', true) shouldBe 221u
|
||||
encodeS('│', false) shouldBe 93u
|
||||
encodeS('│', true) shouldBe 93u
|
||||
|
||||
// underscore
|
||||
encodeP('_', false) shouldBe 228u
|
||||
encodeP('_', true) shouldBe 228u
|
||||
encodeS('_', false) shouldBe 100u
|
||||
encodeS('_', true) shouldBe 100u
|
||||
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
|
||||
encodeP('▁', false) shouldBe 228u
|
||||
encodeP('▁', true) shouldBe 228u
|
||||
encodeS('▁', false) shouldBe 100u
|
||||
encodeS('▁', true) shouldBe 100u
|
||||
|
||||
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||
encodeP('─', false) shouldBe 192u
|
||||
encodeP('─', true) shouldBe 192u
|
||||
encodeS('─', false) shouldBe 64u
|
||||
encodeS('─', true) shouldBe 64u
|
||||
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
||||
encodeP('│', false) shouldBe 221u
|
||||
encodeP('│', true) shouldBe 221u
|
||||
encodeS('│', false) shouldBe 93u
|
||||
encodeS('│', true) shouldBe 93u
|
||||
}
|
||||
|
||||
test("testBoxDrawingCharsDecoding") {
|
||||
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||
PetsciiEncoding.decodePetscii(listOf(195u), false).getOrElse { throw it }.single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
|
||||
PetsciiEncoding.decodePetscii(listOf(195u), true).getOrElse { throw it }.single() shouldBe 'C'
|
||||
PetsciiEncoding.decodePetscii(listOf(192u), false).getOrElse { throw it }.single() shouldBe '─'
|
||||
PetsciiEncoding.decodePetscii(listOf(192u), true).getOrElse { throw it }.single() shouldBe '─'
|
||||
PetsciiEncoding.decodeScreencode(listOf(67u), false).getOrElse { throw it }.single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
|
||||
PetsciiEncoding.decodeScreencode(listOf(67u), true).getOrElse { throw it }.single() shouldBe 'C'
|
||||
PetsciiEncoding.decodeScreencode(listOf(64u), false).getOrElse { throw it }.single() shouldBe '─'
|
||||
PetsciiEncoding.decodeScreencode(listOf(64u), true).getOrElse { throw it }.single() shouldBe '─'
|
||||
|
||||
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
||||
PetsciiEncoding.decodePetscii(listOf(125u), false).getOrElse { throw it }.single() shouldBe '│'
|
||||
PetsciiEncoding.decodePetscii(listOf(125u), true).getOrElse { throw it }.single() shouldBe '│'
|
||||
PetsciiEncoding.decodePetscii(listOf(221u), false).getOrElse { throw it }.single() shouldBe '│'
|
||||
PetsciiEncoding.decodePetscii(listOf(221u), true).getOrElse { throw it }.single() shouldBe '│'
|
||||
PetsciiEncoding.decodeScreencode(listOf(93u), false).getOrElse { throw it }.single() shouldBe '│'
|
||||
PetsciiEncoding.decodeScreencode(listOf(93u), true).getOrElse { throw it }.single() shouldBe '│'
|
||||
PetsciiEncoding.decodeScreencode(listOf(66u), false).getOrElse { throw it }.single() shouldBe '\uf13c' // "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)"
|
||||
PetsciiEncoding.decodeScreencode(listOf(66u), true).getOrElse { throw it }.single() shouldBe 'B'
|
||||
}
|
||||
}
|
||||
|
||||
test("testErrorCases") {
|
||||
Petscii.encodePetscii("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||
Petscii.encodePetscii("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||
Petscii.encodeScreencode("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||
Petscii.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||
context("iso") {
|
||||
test("iso accepts iso-characters") {
|
||||
val result = IsoEncoding.encode("a_~ëç")
|
||||
result.getOrElse { throw it }.map {it.toInt()} shouldBe listOf(97, 95, 126, 235, 231)
|
||||
}
|
||||
|
||||
test("non-iso doesn't accept iso-characters") {
|
||||
var result = PetsciiEncoding.encodePetscii("a_~ë")
|
||||
result.expectError { "should not encode" }
|
||||
result = PetsciiEncoding.encodeScreencode("a_~ë")
|
||||
result.expectError { "should not encode" }
|
||||
}
|
||||
}
|
||||
|
||||
test("testSpecialReplacements") {
|
||||
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
|
||||
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
|
||||
|
||||
Petscii.encodePetscii("`", false).expectError { "shouldn't have translation for backtick" }
|
||||
Petscii.encodePetscii("`", true).expectError { "shouldn't have translation for backtick" }
|
||||
Petscii.encodePetscii("~", false).expectError { "shouldn't have translation for tilde" }
|
||||
Petscii.encodePetscii("~", true).expectError { "shouldn't have translation for tilde" }
|
||||
|
||||
encodeP('^', false) shouldBe 94u
|
||||
encodeP('^', true) shouldBe 94u
|
||||
encodeS('^', false) shouldBe 30u
|
||||
encodeS('^', true) shouldBe 30u
|
||||
encodeP('_', false) shouldBe 228u
|
||||
encodeP('_', true) shouldBe 228u
|
||||
encodeS('_', false) shouldBe 100u
|
||||
encodeS('_', true) shouldBe 100u
|
||||
encodeP('{', false) shouldBe 243u
|
||||
encodeP('{', true) shouldBe 243u
|
||||
encodeS('{', false) shouldBe 115u
|
||||
encodeS('{', true) shouldBe 115u
|
||||
encodeP('}', false) shouldBe 235u
|
||||
encodeP('}', true) shouldBe 235u
|
||||
encodeS('}', false) shouldBe 107u
|
||||
encodeS('}', true) shouldBe 107u
|
||||
encodeP('|', false) shouldBe 221u
|
||||
encodeP('|', true) shouldBe 221u
|
||||
encodeS('|', false) shouldBe 93u
|
||||
encodeS('|', true) shouldBe 93u
|
||||
encodeP('\\', false) shouldBe 205u
|
||||
encodeP('\\', true) shouldBe 205u
|
||||
encodeS('\\', false) shouldBe 77u
|
||||
encodeS('\\', true) shouldBe 77u
|
||||
}
|
||||
|
||||
test("testBoxDrawingCharsEncoding") {
|
||||
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
|
||||
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
|
||||
|
||||
// pipe char
|
||||
encodeP('|', false) shouldBe 221u
|
||||
encodeP('|', true) shouldBe 221u
|
||||
encodeS('|', false) shouldBe 93u
|
||||
encodeS('|', true) shouldBe 93u
|
||||
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
|
||||
encodeP('│', false) shouldBe 221u
|
||||
encodeP('│', true) shouldBe 221u
|
||||
encodeS('│', false) shouldBe 93u
|
||||
encodeS('│', true) shouldBe 93u
|
||||
|
||||
// underscore
|
||||
encodeP('_', false) shouldBe 228u
|
||||
encodeP('_', true) shouldBe 228u
|
||||
encodeS('_', false) shouldBe 100u
|
||||
encodeS('_', true) shouldBe 100u
|
||||
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
|
||||
encodeP('▁', false) shouldBe 228u
|
||||
encodeP('▁', true) shouldBe 228u
|
||||
encodeS('▁', false) shouldBe 100u
|
||||
encodeS('▁', true) shouldBe 100u
|
||||
|
||||
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||
encodeP('─', false) shouldBe 192u
|
||||
encodeP('─', true) shouldBe 192u
|
||||
encodeS('─', false) shouldBe 64u
|
||||
encodeS('─', true) shouldBe 64u
|
||||
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
||||
encodeP('│', false) shouldBe 221u
|
||||
encodeP('│', true) shouldBe 221u
|
||||
encodeS('│', false) shouldBe 93u
|
||||
encodeS('│', true) shouldBe 93u
|
||||
}
|
||||
|
||||
test("testBoxDrawingCharsDecoding") {
|
||||
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||
Petscii.decodePetscii(listOf(195u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
|
||||
Petscii.decodePetscii(listOf(195u), true).single() shouldBe 'C'
|
||||
Petscii.decodePetscii(listOf(192u), false).single() shouldBe '─'
|
||||
Petscii.decodePetscii(listOf(192u), true).single() shouldBe '─'
|
||||
Petscii.decodeScreencode(listOf(67u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
|
||||
Petscii.decodeScreencode(listOf(67u), true).single() shouldBe 'C'
|
||||
Petscii.decodeScreencode(listOf(64u), false).single() shouldBe '─'
|
||||
Petscii.decodeScreencode(listOf(64u), true).single() shouldBe '─'
|
||||
|
||||
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
||||
Petscii.decodePetscii(listOf(125u), false).single() shouldBe '│'
|
||||
Petscii.decodePetscii(listOf(125u), true).single() shouldBe '│'
|
||||
Petscii.decodePetscii(listOf(221u), false).single() shouldBe '│'
|
||||
Petscii.decodePetscii(listOf(221u), true).single() shouldBe '│'
|
||||
Petscii.decodeScreencode(listOf(93u), false).single() shouldBe '│'
|
||||
Petscii.decodeScreencode(listOf(93u), true).single() shouldBe '│'
|
||||
Petscii.decodeScreencode(listOf(66u), false).single() shouldBe '\uf13c' // "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)"
|
||||
Petscii.decodeScreencode(listOf(66u), true).single() shouldBe 'B'
|
||||
}
|
||||
|
||||
})
|
||||
@@ -0,0 +1,146 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.PipeExpression
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.Pipe
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
class TestPipes: FunSpec({
|
||||
|
||||
test("correct pipe statements") {
|
||||
val text = """
|
||||
%import floats
|
||||
%import textio
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
1.234 |> addfloat
|
||||
|> floats.print_f
|
||||
|
||||
9999 |> addword
|
||||
|> txt.print_uw
|
||||
|
||||
}
|
||||
|
||||
sub addfloat(float fl) -> float {
|
||||
return fl+2.22
|
||||
}
|
||||
sub addword(uword ww) -> uword {
|
||||
return ww+2222
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, true, text, writeAssembly = true).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 3
|
||||
val pipef = stmts[0] as Pipe
|
||||
pipef.expressions.size shouldBe 2
|
||||
pipef.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
|
||||
pipef.expressions[1] shouldBe instanceOf<IdentifierReference>()
|
||||
|
||||
val pipew = stmts[1] as Pipe
|
||||
pipew.expressions.size shouldBe 2
|
||||
pipew.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
|
||||
pipew.expressions[1] shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
|
||||
test("incorrect type in pipe statement") {
|
||||
val text = """
|
||||
%option enable_floats
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
1.234 |> addfloat
|
||||
|> addword |> addword
|
||||
}
|
||||
|
||||
sub addfloat(float fl) -> float {
|
||||
return fl+2.22
|
||||
}
|
||||
sub addword(uword ww) -> uword {
|
||||
return ww+2222
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "incompatible"
|
||||
}
|
||||
|
||||
test("correct pipe expressions") {
|
||||
val text = """
|
||||
%import floats
|
||||
%import textio
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
float @shared fl = 1.234 |> addfloat
|
||||
|> addfloat
|
||||
|
||||
uword @shared ww = 9999 |> addword
|
||||
|> addword
|
||||
|
||||
}
|
||||
|
||||
sub addfloat(float fl) -> float {
|
||||
return fl+2.22
|
||||
}
|
||||
sub addword(uword ww) -> uword {
|
||||
return ww+2222
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, true, text, writeAssembly = true).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 5
|
||||
val assignf = stmts[1] as Assignment
|
||||
val pipef = assignf.value as PipeExpression
|
||||
pipef.expressions.size shouldBe 2
|
||||
pipef.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
|
||||
pipef.expressions[1] shouldBe instanceOf<IdentifierReference>()
|
||||
|
||||
val assignw = stmts[3] as Assignment
|
||||
val pipew = assignw.value as PipeExpression
|
||||
pipew.expressions.size shouldBe 2
|
||||
pipew.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
|
||||
pipew.expressions[1] shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
|
||||
test("incorrect type in pipe expression") {
|
||||
val text = """
|
||||
%option enable_floats
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
uword result = 1.234 |> addfloat
|
||||
|> addword |> addword
|
||||
}
|
||||
|
||||
sub addfloat(float fl) -> float {
|
||||
return fl+2.22
|
||||
}
|
||||
sub addword(uword ww) -> uword {
|
||||
return ww+2222
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "incompatible"
|
||||
}
|
||||
})
|
||||
@@ -9,7 +9,6 @@ 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.codegen.target.C64Target
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.compiler.printProgram
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
class TestTypecasts: FunSpec({
|
||||
|
||||
test("correct typecasts") {
|
||||
val text = """
|
||||
%import floats
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
float @shared fl = 3.456
|
||||
uword @shared uw = 5555
|
||||
byte @shared bb = -44
|
||||
|
||||
bb = uw as byte
|
||||
uw = bb as uword
|
||||
fl = uw as float
|
||||
fl = bb as float
|
||||
bb = fl as byte
|
||||
uw = fl as uword
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
|
||||
result.program.entrypoint.statements.size shouldBe 13
|
||||
}
|
||||
|
||||
test("invalid typecasts of numbers") {
|
||||
val text = """
|
||||
%import floats
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared bb
|
||||
|
||||
bb = 5555 as ubyte
|
||||
routine(5555 as ubyte)
|
||||
}
|
||||
|
||||
sub routine(ubyte bb) {
|
||||
bb++
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = true, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "can't cast"
|
||||
errors.errors[1] shouldContain "can't cast"
|
||||
}
|
||||
|
||||
test("refuse to round float literal 1") {
|
||||
val text = """
|
||||
%option enable_floats
|
||||
main {
|
||||
sub start() {
|
||||
float @shared fl = 3.456 as uword
|
||||
fl = 1.234 as uword
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "can't cast"
|
||||
errors.errors[1] shouldContain "can't cast"
|
||||
}
|
||||
|
||||
test("refuse to round float literal 2") {
|
||||
val text = """
|
||||
%option enable_floats
|
||||
main {
|
||||
sub start() {
|
||||
float @shared fl = 3.456
|
||||
fl++
|
||||
fl = fl as uword
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "in-place makes no sense"
|
||||
}
|
||||
|
||||
test("refuse to round float literal 3") {
|
||||
val text = """
|
||||
%option enable_floats
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared ww = 3.456 as uword
|
||||
ww++
|
||||
ww = 3.456 as uword
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "can't cast"
|
||||
errors.errors[1] shouldContain "can't cast"
|
||||
}
|
||||
|
||||
test("correct implicit casts of signed number comparison and logical expressions") {
|
||||
val text = """
|
||||
%import floats
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
byte bb = -10
|
||||
word ww = -1000
|
||||
|
||||
if bb>0 {
|
||||
bb++
|
||||
}
|
||||
if bb < 0 {
|
||||
bb ++
|
||||
}
|
||||
if bb & 1 {
|
||||
bb++
|
||||
}
|
||||
if bb & 128 {
|
||||
bb++
|
||||
}
|
||||
if bb & 255 {
|
||||
bb++
|
||||
}
|
||||
|
||||
if ww>0 {
|
||||
ww++
|
||||
}
|
||||
if ww < 0 {
|
||||
ww ++
|
||||
}
|
||||
if ww & 1 {
|
||||
ww++
|
||||
}
|
||||
if ww & 32768 {
|
||||
ww++
|
||||
}
|
||||
if ww & 65535 {
|
||||
ww++
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
|
||||
printProgram(result.program)
|
||||
val statements = result.program.entrypoint.statements
|
||||
statements.size shouldBe 27
|
||||
}
|
||||
|
||||
test("cast to unsigned in conditional") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
byte bb
|
||||
word ww
|
||||
|
||||
ubyte iteration_in_progress
|
||||
uword num_bytes
|
||||
|
||||
if not iteration_in_progress or not num_bytes {
|
||||
num_bytes++
|
||||
}
|
||||
|
||||
if bb as ubyte {
|
||||
bb++
|
||||
}
|
||||
if ww as uword {
|
||||
ww++
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
|
||||
printProgram(result.program)
|
||||
val statements = result.program.entrypoint.statements
|
||||
statements.size shouldBe 16
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,9 @@
|
||||
package prog8tests
|
||||
|
||||
import com.github.michaelbull.result.expectError
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import com.github.michaelbull.result.onFailure
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
@@ -7,48 +11,20 @@ import io.kotest.matchers.collections.shouldBeIn
|
||||
import io.kotest.matchers.collections.shouldNotBeIn
|
||||
import io.kotest.matchers.comparables.shouldBeGreaterThan
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.codegen.target.c64.C64Zeropage
|
||||
import prog8.codegen.target.cx16.CX16Zeropage
|
||||
import prog8.compilerinterface.*
|
||||
import prog8tests.helpers.DummyCompilationTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
|
||||
class TestAbstractZeropage: FunSpec({
|
||||
|
||||
class DummyCompilationTarget: ICompilationTarget {
|
||||
override val name: String = "dummy"
|
||||
override val machine: IMachineDefinition
|
||||
get() = throw NotImplementedError("dummy")
|
||||
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
paramRegisters: List<RegisterOrStatusflag>): Boolean {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DummyZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
override val SCRATCH_B1 = 0x10u
|
||||
override val SCRATCH_REG = 0x11u
|
||||
@@ -64,7 +40,6 @@ class TestAbstractZeropage: FunSpec({
|
||||
|
||||
|
||||
test("testAbstractZeropage") {
|
||||
val compTarget = DummyCompilationTarget()
|
||||
val zp = DummyZeropage(
|
||||
CompilationOptions(
|
||||
OutputType.RAW,
|
||||
@@ -73,7 +48,7 @@ class TestAbstractZeropage: FunSpec({
|
||||
listOf((0x50u..0x5fu)),
|
||||
false,
|
||||
false,
|
||||
compTarget
|
||||
DummyCompilationTarget
|
||||
)
|
||||
)
|
||||
zp.free.size shouldBe 256-6-16
|
||||
@@ -89,26 +64,26 @@ class TestC64Zeropage: FunSpec({
|
||||
test("testNames") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
||||
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||
}
|
||||
zp.allocate("varname2", DataType.UBYTE, null, errors)
|
||||
var result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
|
||||
result.onFailure { fail(it.toString()) }
|
||||
result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
|
||||
result.onFailure { fail(it.toString()) }
|
||||
result = zp.allocate(listOf("varname"), DataType.UBYTE, null, null, errors)
|
||||
result.onFailure { fail(it.toString()) }
|
||||
shouldThrow<IllegalArgumentException> { zp.allocate(listOf("varname"), DataType.UBYTE, null, null, errors) }
|
||||
result = zp.allocate(listOf("varname2"), DataType.UBYTE, null, null, errors)
|
||||
result.onFailure { fail(it.toString()) }
|
||||
}
|
||||
|
||||
test("testZpFloatEnable") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
shouldThrow<InternalCompilerException> {
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled floats" }
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
|
||||
shouldThrow<InternalCompilerException> {
|
||||
zp2.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
result = zp2.allocate(emptyList(), DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled ZP use" }
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
|
||||
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||
zp3.allocate(emptyList(), DataType.FLOAT, null, null, errors)
|
||||
}
|
||||
|
||||
test("testZpModesWithFloats") {
|
||||
@@ -130,9 +105,8 @@ class TestC64Zeropage: FunSpec({
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
|
||||
println(zp.free)
|
||||
zp.availableBytes() shouldBe 0
|
||||
shouldThrow<InternalCompilerException> {
|
||||
zp.allocate("", DataType.BYTE, null, errors)
|
||||
}
|
||||
val result = zp.allocate(emptyList(), DataType.BYTE, null, null, errors)
|
||||
result.expectError { "expected error due to disabled ZP use" }
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
@@ -144,9 +118,9 @@ class TestC64Zeropage: FunSpec({
|
||||
zp3.availableBytes() shouldBe 125
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
zp4.availableBytes() shouldBe 239
|
||||
zp4.allocate("test", DataType.UBYTE, null, errors)
|
||||
zp4.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||
zp4.availableBytes() shouldBe 238
|
||||
zp4.allocate("test2", DataType.UBYTE, null, errors)
|
||||
zp4.allocate(listOf("test2"), DataType.UBYTE, null, null, errors)
|
||||
zp4.availableBytes() shouldBe 237
|
||||
}
|
||||
|
||||
@@ -177,24 +151,20 @@ class TestC64Zeropage: FunSpec({
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
// in regular zp there aren't 5 sequential bytes free
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "expect allocation error: in regular zp there aren't 5 sequential bytes free" }
|
||||
|
||||
for (i in 0 until zp.availableBytes()) {
|
||||
val loc = zp.allocate("", DataType.UBYTE, null, errors)
|
||||
loc shouldBeGreaterThan 0u
|
||||
val alloc = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
|
||||
alloc.getOrElse { throw it }
|
||||
}
|
||||
zp.availableBytes() shouldBe 0
|
||||
zp.hasByteAvailable() shouldBe false
|
||||
zp.hasWordAvailable() shouldBe false
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
|
||||
result.expectError { "expected allocation error" }
|
||||
result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
|
||||
result.expectError { "expected allocation error" }
|
||||
}
|
||||
|
||||
test("testFullAllocation") {
|
||||
@@ -202,49 +172,47 @@ class TestC64Zeropage: FunSpec({
|
||||
zp.availableBytes() shouldBe 239
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||
var result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
|
||||
val loc = result.getOrElse { throw it } .first
|
||||
loc shouldBeGreaterThan 3u
|
||||
loc shouldNotBeIn zp.free
|
||||
val num = zp.availableBytes() / 2
|
||||
|
||||
for(i in 0..num-3) {
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
|
||||
}
|
||||
zp.availableBytes() shouldBe 5
|
||||
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
// can't allocate because no more sequential bytes, only fragmented
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
// can't allocate because no more sequential bytes, only fragmented
|
||||
result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors)
|
||||
result.expectError { "should give allocation error" }
|
||||
|
||||
for(i in 0..4) {
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
|
||||
}
|
||||
|
||||
zp.availableBytes() shouldBe 0
|
||||
zp.hasByteAvailable() shouldBe false
|
||||
zp.hasWordAvailable() shouldBe false
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
// no more space
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors)
|
||||
result.expectError { "should give allocation error" }
|
||||
}
|
||||
|
||||
test("testEfficientAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
zp.availableBytes() shouldBe 18
|
||||
zp.allocate("", DataType.WORD, null, errors) shouldBe 0x04u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0au
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9bu
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9eu
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5u
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0u
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbeu
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0eu
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9u
|
||||
zp.allocate(emptyList(), DataType.WORD, null, null, errors ).getOrElse{throw it}.first shouldBe 0x04u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x06u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x0au
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x9bu
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x9eu
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xa5u
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xb0u
|
||||
zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xbeu
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x0eu
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x92u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x96u
|
||||
zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0xf9u
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
@@ -274,9 +242,9 @@ class TestCx16Zeropage: FunSpec({
|
||||
zp2.availableBytes() shouldBe 175
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
||||
zp3.availableBytes() shouldBe 216
|
||||
zp3.allocate("test", DataType.UBYTE, null, errors)
|
||||
zp3.allocate(listOf("test"), DataType.UBYTE, null, null, errors)
|
||||
zp3.availableBytes() shouldBe 215
|
||||
zp3.allocate("test2", DataType.UBYTE, null, errors)
|
||||
zp3.allocate(listOf("test2"), DataType.UBYTE, null, null, errors)
|
||||
zp3.availableBytes() shouldBe 214
|
||||
}
|
||||
|
||||
@@ -289,4 +257,15 @@ class TestCx16Zeropage: FunSpec({
|
||||
0x02u shouldNotBeIn zp1.free
|
||||
0x21u shouldNotBeIn zp1.free
|
||||
}
|
||||
|
||||
test("preallocated zp vars") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
||||
zp1.allocatedZeropageVariable(listOf("test")) shouldBe null
|
||||
zp1.allocatedZeropageVariable(listOf("cx16", "r0")) shouldNotBe null
|
||||
zp1.allocatedZeropageVariable(listOf("cx16", "r15")) shouldNotBe null
|
||||
zp1.allocatedZeropageVariable(listOf("cx16", "r0L")) shouldNotBe null
|
||||
zp1.allocatedZeropageVariable(listOf("cx16", "r15L")) shouldNotBe null
|
||||
zp1.allocatedZeropageVariable(listOf("cx16", "r0sH")) shouldNotBe null
|
||||
zp1.allocatedZeropageVariable(listOf("cx16", "r15sH")) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -61,47 +61,91 @@ class TestAstToSourceText: AnnotationSpec() {
|
||||
|
||||
|
||||
@Test
|
||||
fun testStringLiteral_noAlt() {
|
||||
fun testStringLiteral_DefaultEnc() {
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
str s = "fooBar\n"
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex("str +s += +\"fooBar\\\\n\"")
|
||||
txt shouldContain Regex("str +s += +petscii:\"fooBar\\\\n\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStringLiteral_withAlt() {
|
||||
fun testStringLiteral_withSc() {
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
str sAlt = sc:"fooBar\n"
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex("str +sAlt += +sc:\"fooBar\\\\n\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStringLiteral_withOldSc() {
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
str sAlt = @"fooBar\n"
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex("str +sAlt += +@\"fooBar\\\\n\"")
|
||||
txt shouldContain Regex("str +sAlt += +sc:\"fooBar\\\\n\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharLiteral_noAlt() {
|
||||
fun testStringLiteral_withIso() {
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
str sAlt = iso:"fooBar\n"
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex("str +sAlt += +iso:\"fooBar\\\\n\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharLiteral_defaultEnc() {
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
ubyte c = 'x'
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex("ubyte +c += +'x'")
|
||||
txt shouldContain Regex("ubyte +c += +petscii:'x'")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharLiteral_withAlt() {
|
||||
fun testCharLiteral_OldSc() {
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
ubyte cAlt = @'x'
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex("ubyte +cAlt += +@'x'")
|
||||
txt shouldContain Regex("ubyte +cAlt += +sc:'x'")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharLiteral_Sc() {
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
ubyte cAlt = sc:'x'
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex("ubyte +cAlt += +sc:'x'")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVar_withZpAndShared() {
|
||||
val orig = SourceCode.Text("""
|
||||
main {
|
||||
ubyte @shared @zp qq
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex("ubyte +@zp +@shared +qq")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,8 @@ import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cbm.PetsciiEncoding
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
@@ -413,7 +414,7 @@ class TestProg8Parser: FunSpec( {
|
||||
char.value shouldBe '\n'
|
||||
}
|
||||
|
||||
test("on rhs of block-level var decl, no AltEnc") {
|
||||
test("on rhs of block-level var decl, default encoding") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
ubyte c = 'x'
|
||||
@@ -425,11 +426,11 @@ class TestProg8Parser: FunSpec( {
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.encoding shouldBe Encoding.PETSCII
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe false
|
||||
}
|
||||
|
||||
test("on rhs of block-level const decl, with AltEnc") {
|
||||
test("on rhs of block-level const decl, with screencode enc (old syntax)") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
const ubyte c = @'x'
|
||||
@@ -441,11 +442,44 @@ class TestProg8Parser: FunSpec( {
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.encoding shouldBe Encoding.SCREENCODES
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe true
|
||||
}
|
||||
|
||||
test("on rhs of subroutine-level var decl, no AltEnc") {
|
||||
test("on rhs of block-level const decl, with screencode enc (new syntax)") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
const ubyte c = sc:'x'
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.encoding shouldBe Encoding.SCREENCODES
|
||||
rhs.value shouldBe 'x'
|
||||
}
|
||||
|
||||
test("on rhs of block-level const decl, with iso encoding") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
const ubyte c = iso:'_'
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.encoding shouldBe Encoding.ISO
|
||||
rhs.value shouldBe '_'
|
||||
}
|
||||
|
||||
|
||||
test("on rhs of subroutine-level var decl, default encoding") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
@@ -461,10 +495,10 @@ class TestProg8Parser: FunSpec( {
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe false
|
||||
rhs.encoding shouldBe Encoding.PETSCII
|
||||
}
|
||||
|
||||
test("on rhs of subroutine-level const decl, with AltEnc") {
|
||||
test("on rhs of subroutine-level const decl, screencode (old syntax)") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
@@ -479,8 +513,99 @@ class TestProg8Parser: FunSpec( {
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.encoding shouldBe Encoding.SCREENCODES
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe true
|
||||
}
|
||||
|
||||
test("on rhs of subroutine-level const decl, screencode (new syntax)") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
const ubyte c = sc:'x'
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.encoding shouldBe Encoding.SCREENCODES
|
||||
rhs.value shouldBe 'x'
|
||||
}
|
||||
|
||||
test("on rhs of subroutine-level const decl, iso encoding") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
const ubyte c = iso:'_'
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.encoding shouldBe Encoding.ISO
|
||||
rhs.value shouldBe '_'
|
||||
}
|
||||
}
|
||||
|
||||
context("Strings") {
|
||||
|
||||
test("default encoding") {
|
||||
val source = """
|
||||
main {
|
||||
str name = "name"
|
||||
}"""
|
||||
val module = parseModule(SourceCode.Text(source))
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
val rhs = decl.value as StringLiteralValue
|
||||
rhs.encoding shouldBe Encoding.PETSCII
|
||||
rhs.value shouldBe "name"
|
||||
}
|
||||
|
||||
test("old syntax alt encoding") {
|
||||
val source = """
|
||||
main {
|
||||
str name = @"name"
|
||||
}"""
|
||||
val module = parseModule(SourceCode.Text(source))
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
val rhs = decl.value as StringLiteralValue
|
||||
rhs.encoding shouldBe Encoding.SCREENCODES
|
||||
rhs.value shouldBe "name"
|
||||
}
|
||||
|
||||
test("new syntax encodings") {
|
||||
val source = """
|
||||
main {
|
||||
str name1 = petscii:"Name"
|
||||
str name2 = sc:"Name"
|
||||
str name3 = iso:"Name"
|
||||
}"""
|
||||
val module = parseModule(SourceCode.Text(source))
|
||||
val (decl1, decl2, decl3) = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()
|
||||
val rhs1 = decl1.value as StringLiteralValue
|
||||
val rhs2 = decl2.value as StringLiteralValue
|
||||
val rhs3 = decl3.value as StringLiteralValue
|
||||
rhs1.encoding shouldBe Encoding.PETSCII
|
||||
rhs1.value shouldBe "Name"
|
||||
rhs2.encoding shouldBe Encoding.SCREENCODES
|
||||
rhs2.value shouldBe "Name"
|
||||
rhs3.encoding shouldBe Encoding.ISO
|
||||
rhs3.value shouldBe "Name"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,34 +637,36 @@ class TestProg8Parser: FunSpec( {
|
||||
|
||||
iterables.size shouldBe 5
|
||||
|
||||
val it0 = iterables[0] as RangeExpr
|
||||
val it0 = iterables[0] as RangeExpression
|
||||
it0.from shouldBe instanceOf<StringLiteralValue>()
|
||||
it0.to shouldBe instanceOf<StringLiteralValue>()
|
||||
|
||||
val it1 = iterables[1] as StringLiteralValue
|
||||
it1.value shouldBe "something"
|
||||
|
||||
val it2 = iterables[2] as RangeExpr
|
||||
val it2 = iterables[2] as RangeExpression
|
||||
it2.from shouldBe instanceOf<CharLiteral>()
|
||||
it2.to shouldBe instanceOf<CharLiteral>()
|
||||
|
||||
val it3 = iterables[3] as RangeExpr
|
||||
val it3 = iterables[3] as RangeExpression
|
||||
it3.from shouldBe instanceOf<NumericLiteralValue>()
|
||||
it3.to shouldBe instanceOf<NumericLiteralValue>()
|
||||
|
||||
val it4 = iterables[4] as RangeExpr
|
||||
val it4 = iterables[4] as RangeExpression
|
||||
it4.from shouldBe instanceOf<NumericLiteralValue>()
|
||||
it4.to shouldBe instanceOf<NumericLiteralValue>()
|
||||
}
|
||||
}
|
||||
|
||||
test("testCharLiteralConstValue") {
|
||||
val char1 = CharLiteral('A', false, Position.DUMMY)
|
||||
val char2 = CharLiteral('z', true, Position.DUMMY)
|
||||
val char1 = CharLiteral('A', Encoding.PETSCII, Position.DUMMY)
|
||||
val char2 = CharLiteral('z', Encoding.SCREENCODES, Position.DUMMY)
|
||||
val char3 = CharLiteral('_', Encoding.ISO, Position.DUMMY)
|
||||
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, AsciiStringEncoder)
|
||||
char1.constValue(program).number.toInt() shouldBe 65
|
||||
char2.constValue(program).number.toInt() shouldBe 122
|
||||
char3.constValue(program).number.toInt() shouldBe 95
|
||||
}
|
||||
|
||||
test("testLiteralValueComparisons") {
|
||||
@@ -560,8 +687,8 @@ class TestProg8Parser: FunSpec( {
|
||||
(ten <= ten) shouldBe true
|
||||
(ten < ten) shouldBe false
|
||||
|
||||
val abc = StringLiteralValue("abc", false, Position.DUMMY)
|
||||
val abd = StringLiteralValue("abd", false, Position.DUMMY)
|
||||
val abc = StringLiteralValue("abc", Encoding.PETSCII, Position.DUMMY)
|
||||
val abd = StringLiteralValue("abd", Encoding.PETSCII, Position.DUMMY)
|
||||
abc shouldBe abc
|
||||
(abc!=abd) shouldBe true
|
||||
(abc!=abc) shouldBe false
|
||||
@@ -726,7 +853,6 @@ class TestProg8Parser: FunSpec( {
|
||||
ubexpr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
|
||||
test("assignment isAugmented correctness") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
@@ -796,8 +922,91 @@ class TestProg8Parser: FunSpec( {
|
||||
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()
|
||||
// TODO characters should perhaps not be encoded until code generation, like strings... however this will prevent optimizing expressions with characters
|
||||
val encodedletter = PetsciiEncoding.encodePetscii("A", true).getOrElse { fail("petscii error") }.single()
|
||||
letter.value shouldBe NumericLiteralValue(DataType.UBYTE, encodedletter.toDouble(), Position.DUMMY)
|
||||
}
|
||||
|
||||
test("`in` containment checks") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
str string = "hello"
|
||||
ubyte[] array = [1,2,3,4]
|
||||
|
||||
ubyte cc
|
||||
if cc in [' ', '@', 0] {
|
||||
}
|
||||
|
||||
if cc in "email" {
|
||||
}
|
||||
|
||||
cc = 99 in array
|
||||
cc = '@' in string
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
val start = result.program.entrypoint
|
||||
val containmentChecks = start.statements.takeLast(4)
|
||||
(containmentChecks[0] as IfElse).condition shouldBe instanceOf<ContainmentCheck>()
|
||||
(containmentChecks[1] as IfElse).condition shouldBe instanceOf<ContainmentCheck>()
|
||||
(containmentChecks[2] as Assignment).value shouldBe instanceOf<ContainmentCheck>()
|
||||
(containmentChecks[3] as Assignment).value shouldBe instanceOf<ContainmentCheck>()
|
||||
}
|
||||
|
||||
test("invalid `in` containment checks") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte cc
|
||||
ubyte[] array = [1,2,3]
|
||||
cc = 99 in 12345
|
||||
cc = 9999 in array
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors = errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "must be an iterable type"
|
||||
errors.errors[1] shouldContain "datatype doesn't match"
|
||||
}
|
||||
|
||||
test("vardecl options any order okay") {
|
||||
val text="""
|
||||
main {
|
||||
%option force_output
|
||||
sub start() {
|
||||
ubyte @zp @shared @requirezp var1
|
||||
ubyte @shared @zp var2
|
||||
ubyte @zp var3
|
||||
ubyte @shared var4
|
||||
ubyte @requirezp var5
|
||||
ubyte var6
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
val stmt = result.program.entrypoint.statements
|
||||
stmt.size shouldBe 12
|
||||
val var1 = stmt[0] as VarDecl
|
||||
var1.sharedWithAsm shouldBe true
|
||||
var1.zeropage shouldBe ZeropageWish.REQUIRE_ZEROPAGE
|
||||
val var2 = stmt[2] as VarDecl
|
||||
var2.sharedWithAsm shouldBe true
|
||||
var2.zeropage shouldBe ZeropageWish.PREFER_ZEROPAGE
|
||||
val var3 = stmt[4] as VarDecl
|
||||
var3.sharedWithAsm shouldBe false
|
||||
var3.zeropage shouldBe ZeropageWish.PREFER_ZEROPAGE
|
||||
val var4 = stmt[6] as VarDecl
|
||||
var4.sharedWithAsm shouldBe true
|
||||
var4.zeropage shouldBe ZeropageWish.DONTCARE
|
||||
val var5 = stmt[8] as VarDecl
|
||||
var5.sharedWithAsm shouldBe false
|
||||
var5.zeropage shouldBe ZeropageWish.REQUIRE_ZEROPAGE
|
||||
val var6 = stmt[10] as VarDecl
|
||||
var6.sharedWithAsm shouldBe false
|
||||
var6.zeropage shouldBe ZeropageWish.DONTCARE
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,5 @@
|
||||
package prog8tests.codegeneration
|
||||
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.Module
|
||||
@@ -46,30 +45,28 @@ class TestAsmGenSymbols: StringSpec({
|
||||
}
|
||||
|
||||
*/
|
||||
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, null, Position.DUMMY)
|
||||
val varInSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, null, Position.DUMMY)
|
||||
val var2InSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, null, Position.DUMMY)
|
||||
val labelInSub = Label("locallabel", Position.DUMMY)
|
||||
|
||||
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), Position.DUMMY)
|
||||
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
|
||||
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
|
||||
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
|
||||
val labelInBlock = Label("label_outside", Position.DUMMY)
|
||||
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, null, Position.DUMMY)
|
||||
val varInBlock = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, null, Position.DUMMY)
|
||||
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
|
||||
|
||||
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
return program
|
||||
return Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder).addModule(module)
|
||||
}
|
||||
|
||||
fun createTestAsmGen(program: Program): AsmGen {
|
||||
@@ -99,19 +96,19 @@ class TestAsmGenSymbols: StringSpec({
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local variable
|
||||
val localvarIdent = sub.statements.filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
|
||||
val localvarIdent = sub.statements.asSequence().filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
|
||||
asmgen.asmSymbolName(localvarIdent) shouldBe "localvar"
|
||||
asmgen.asmVariableName(localvarIdent) shouldBe "localvar"
|
||||
val localvarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "start", "localvar") }.value as AddressOf).identifier
|
||||
val localvarIdentScoped = (sub.statements.asSequence().filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "start", "localvar") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(localvarIdentScoped) shouldBe "main.start.localvar"
|
||||
asmgen.asmVariableName(localvarIdentScoped) shouldBe "main.start.localvar"
|
||||
|
||||
// variable from outer scope (note that for Variables, no scoping prefix symbols are required,
|
||||
// because they're not outputted as locally scoped symbols for the assembler
|
||||
val scopedVarIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("var_outside") }.value as AddressOf).identifier
|
||||
val scopedVarIdent = (sub.statements.asSequence().filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("var_outside") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(scopedVarIdent) shouldBe "main.var_outside"
|
||||
asmgen.asmVariableName(scopedVarIdent) shouldBe "var_outside"
|
||||
val scopedVarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "var_outside") }.value as AddressOf).identifier
|
||||
val scopedVarIdentScoped = (sub.statements.asSequence().filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "var_outside") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(scopedVarIdentScoped) shouldBe "main.var_outside"
|
||||
asmgen.asmVariableName(scopedVarIdentScoped) shouldBe "main.var_outside"
|
||||
}
|
||||
@@ -122,28 +119,20 @@ class TestAsmGenSymbols: StringSpec({
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local label
|
||||
val localLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("locallabel") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(localLabelIdent) shouldBe "_locallabel"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(localLabelIdent) shouldBe "locallabel"
|
||||
}
|
||||
val localLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","start","locallabel") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(localLabelIdentScoped) shouldBe "main.start._locallabel"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(localLabelIdentScoped) shouldBe "main.start.locallabel"
|
||||
}
|
||||
val localLabelIdent = (sub.statements.asSequence().filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("locallabel") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(localLabelIdent) shouldBe "locallabel"
|
||||
asmgen.asmVariableName(localLabelIdent) shouldBe "locallabel"
|
||||
val localLabelIdentScoped = (sub.statements.asSequence().filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","start","locallabel") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(localLabelIdentScoped) shouldBe "main.start.locallabel"
|
||||
asmgen.asmVariableName(localLabelIdentScoped) shouldBe "main.start.locallabel"
|
||||
|
||||
// label from outer scope needs sope prefixes because it is outputted as a locally scoped symbol for the assembler
|
||||
val scopedLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("label_outside") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(scopedLabelIdent) shouldBe "main._label_outside"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(scopedLabelIdent) shouldBe "label_outside"
|
||||
}
|
||||
val scopedLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","label_outside") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(scopedLabelIdentScoped) shouldBe "main._label_outside"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(scopedLabelIdentScoped) shouldBe "main.label_outside"
|
||||
}
|
||||
val scopedLabelIdent = (sub.statements.asSequence().filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("label_outside") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(scopedLabelIdent) shouldBe "main.label_outside"
|
||||
asmgen.asmVariableName(scopedLabelIdent) shouldBe "label_outside"
|
||||
val scopedLabelIdentScoped = (sub.statements.asSequence().filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","label_outside") }.value as AddressOf).identifier
|
||||
asmgen.asmSymbolName(scopedLabelIdentScoped) shouldBe "main.label_outside"
|
||||
asmgen.asmVariableName(scopedLabelIdentScoped) shouldBe "main.label_outside"
|
||||
}
|
||||
|
||||
"asm names for hooks to zp temp vars" {
|
||||
|
||||
@@ -6,8 +6,9 @@ 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
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compilerinterface.*
|
||||
|
||||
internal val DummyFunctions = object : IBuiltinFunctions {
|
||||
override val names: Set<String> = emptySet()
|
||||
@@ -26,19 +27,46 @@ internal val DummyMemsizer = object : IMemSizer {
|
||||
}
|
||||
|
||||
internal val DummyStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal val AsciiStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> = str.map { it.code.toUByte() }
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> = str.map { it.code.toUByte() }
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
||||
return bytes.joinToString()
|
||||
}
|
||||
}
|
||||
|
||||
internal val DummyCompilationTarget = object : ICompilationTarget {
|
||||
override val name: String = "dummy"
|
||||
override val machine: IMachineDefinition
|
||||
get() = throw NotImplementedError("dummy")
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
paramRegisters: List<RegisterOrStatusflag>): Boolean {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@ package prog8.ast
|
||||
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.Encoding
|
||||
|
||||
|
||||
/**
|
||||
@@ -122,6 +124,8 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
|
||||
if(decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
||||
output(" @zp")
|
||||
if(decl.sharedWithAsm)
|
||||
output(" @shared")
|
||||
output(" ${decl.name} ")
|
||||
if(decl.value!=null) {
|
||||
output("= ")
|
||||
@@ -201,7 +205,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) {
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
printout(functionCallExpr as IFunctionCall)
|
||||
}
|
||||
|
||||
@@ -262,7 +266,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(range: RangeExpr) {
|
||||
override fun visit(range: RangeExpression) {
|
||||
range.from.accept(this)
|
||||
output(" to ")
|
||||
range.to.accept(this)
|
||||
@@ -284,15 +288,11 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(char: CharLiteral) {
|
||||
if (char.altEncoding)
|
||||
output("@")
|
||||
output("'${escape(char.value.toString())}'")
|
||||
output("${char.encoding.prefix}:'${escape(char.value.toString())}'")
|
||||
}
|
||||
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
if (string.altEncoding)
|
||||
output("@")
|
||||
output("\"${escape(string.value)}\"")
|
||||
output("${string.encoding.prefix}:\"${escape(string.value)}\"")
|
||||
}
|
||||
|
||||
override fun visit(array: ArrayLiteralValue) {
|
||||
@@ -457,4 +457,24 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
whenChoice.statements.accept(this)
|
||||
outputln("")
|
||||
}
|
||||
|
||||
override fun visit(pipe: Pipe) {
|
||||
printPipe(pipe.expressions)
|
||||
}
|
||||
|
||||
override fun visit(pipe: PipeExpression) {
|
||||
printPipe(pipe.expressions)
|
||||
}
|
||||
|
||||
private fun printPipe(expressions: Iterable<Expression>) {
|
||||
expressions.first().accept(this)
|
||||
outputln("")
|
||||
scopelevel++
|
||||
expressions.drop(1).forEach {
|
||||
outputi("|> ")
|
||||
it.accept(this)
|
||||
outputln("")
|
||||
}
|
||||
scopelevel--
|
||||
}
|
||||
}
|
||||
@@ -133,9 +133,9 @@ interface IStatementContainer {
|
||||
return null
|
||||
}
|
||||
|
||||
val allDefinedSymbols: List<Pair<String, Statement>>
|
||||
val allDefinedSymbols: Sequence<Pair<String, Statement>>
|
||||
get() {
|
||||
return statements.filterIsInstance<INamedStatement>().map { Pair(it.name, it as Statement) }
|
||||
return statements.asSequence().filterIsInstance<INamedStatement>().map { Pair(it.name, it as Statement) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,8 @@ import prog8.ast.base.Position
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.ContainmentCheck
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.ZeropageWish
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
import prog8.parser.SourceCode
|
||||
@@ -77,7 +75,7 @@ class Program(val name: String,
|
||||
get() = toplevelModule.loadAddress
|
||||
|
||||
var actualLoadAddress = 0u
|
||||
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
|
||||
private val internedStringsUnique = mutableMapOf<Pair<String, Encoding>, List<String>>()
|
||||
|
||||
fun internString(string: StringLiteralValue): List<String> {
|
||||
// Move a string literal into the internal, deduplicated, string pool
|
||||
@@ -94,15 +92,15 @@ class Program(val name: String,
|
||||
.first { it is Block && it.name == internedStringsModuleName } as Block
|
||||
val varName = "string_${internedStringsBlock.statements.size}"
|
||||
val decl = VarDecl(
|
||||
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
|
||||
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, subroutineParameter = null, position = string.position
|
||||
VarDeclType.VAR, VarDeclOrigin.STRINGLITERAL, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
|
||||
isArray = false, sharedWithAsm = false, subroutineParameter = null, position = string.position
|
||||
)
|
||||
internedStringsBlock.statements.add(decl)
|
||||
decl.linkParents(internedStringsBlock)
|
||||
return listOf(internedStringsModuleName, decl.name)
|
||||
}
|
||||
|
||||
val key = Pair(string.value, string.altEncoding)
|
||||
val key = Pair(string.value, string.encoding)
|
||||
val existing = internedStringsUnique[key]
|
||||
if (existing != null)
|
||||
return existing
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.antlr.v4.runtime.tree.TerminalNode
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compilerinterface.Encoding
|
||||
import prog8.parser.Prog8ANTLRParser
|
||||
import prog8.parser.SourceCode
|
||||
import java.nio.file.Path
|
||||
@@ -50,59 +51,22 @@ private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList<Stateme
|
||||
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||
|
||||
private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
|
||||
vardecl()?.let { return it.toAst() }
|
||||
vardecl()?.let {
|
||||
return it.toAst(VarDeclType.VAR, null)
|
||||
}
|
||||
|
||||
varinitializer()?.let {
|
||||
val vd = it.vardecl()
|
||||
return VarDecl(
|
||||
VarDeclType.VAR,
|
||||
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.varname.text,
|
||||
it.expression().toAst(),
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
vd.SHARED()!=null,
|
||||
null,
|
||||
it.toPosition()
|
||||
)
|
||||
return it.vardecl().toAst(VarDeclType.VAR, it.expression().toAst())
|
||||
}
|
||||
|
||||
constdecl()?.let {
|
||||
val cvarinit = it.varinitializer()
|
||||
val vd = cvarinit.vardecl()
|
||||
return VarDecl(
|
||||
VarDeclType.CONST,
|
||||
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.varname.text,
|
||||
cvarinit.expression().toAst(),
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
vd.SHARED() != null,
|
||||
null,
|
||||
cvarinit.toPosition()
|
||||
)
|
||||
return cvarinit.vardecl().toAst(VarDeclType.CONST, cvarinit.expression().toAst())
|
||||
}
|
||||
|
||||
memoryvardecl()?.let {
|
||||
val mvarinit = it.varinitializer()
|
||||
val vd = mvarinit.vardecl()
|
||||
return VarDecl(
|
||||
VarDeclType.MEMORY,
|
||||
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
vd.arrayindex()?.toAst(),
|
||||
vd.varname.text,
|
||||
mvarinit.expression().toAst(),
|
||||
vd.ARRAYSIG() != null || vd.arrayindex() != null,
|
||||
false,
|
||||
vd.SHARED()!=null,
|
||||
null,
|
||||
mvarinit.toPosition()
|
||||
)
|
||||
return mvarinit.vardecl().toAst(VarDeclType.MEMORY, mvarinit.expression().toAst())
|
||||
}
|
||||
|
||||
throw FatalAstException("weird variable decl $this")
|
||||
@@ -122,7 +86,7 @@ private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
|
||||
if(vardecl!=null) return vardecl
|
||||
|
||||
assignment()?.let {
|
||||
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
|
||||
return Assignment(it.assign_target().toAst(), it.expression().toAst(), AssignmentOrigin.USERCODE, it.toPosition())
|
||||
}
|
||||
|
||||
augassignment()?.let {
|
||||
@@ -130,7 +94,7 @@ private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
|
||||
val target = it.assign_target().toAst()
|
||||
val oper = it.operator.text.substringBefore('=')
|
||||
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
|
||||
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
|
||||
return Assignment(it.assign_target().toAst(), expression, AssignmentOrigin.USERCODE, it.toPosition())
|
||||
}
|
||||
|
||||
postincrdecr()?.let {
|
||||
@@ -182,6 +146,9 @@ private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
if(whenstmt!=null) return whenstmt
|
||||
|
||||
val pipestmt = pipestmt()?.toAst()
|
||||
if(pipestmt!=null) return pipestmt
|
||||
|
||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||
}
|
||||
|
||||
@@ -230,8 +197,7 @@ private class AsmSubroutineParameter(name: String,
|
||||
|
||||
private class AsmSubroutineReturn(val type: DataType,
|
||||
val registerOrPair: RegisterOrPair?,
|
||||
val statusflag: Statusflag?,
|
||||
val position: Position)
|
||||
val statusflag: Statusflag?)
|
||||
|
||||
private fun Prog8ANTLRParser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||
= asmsub_return().map {
|
||||
@@ -248,8 +214,7 @@ private fun Prog8ANTLRParser.Asmsub_returnsContext.toAst(): List<AsmSubroutineRe
|
||||
AsmSubroutineReturn(
|
||||
it.datatype().toAst(),
|
||||
registerorpair,
|
||||
statusregister,
|
||||
toPosition())
|
||||
statusregister)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||
@@ -280,12 +245,12 @@ private fun Prog8ANTLRParser.Functioncall_stmtContext.toAst(): Statement {
|
||||
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCallExpr {
|
||||
private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCallExpression {
|
||||
val location = scoped_identifier().toAst()
|
||||
return if(expression_list() == null)
|
||||
FunctionCallExpr(location, mutableListOf(), toPosition())
|
||||
FunctionCallExpression(location, mutableListOf(), toPosition())
|
||||
else
|
||||
FunctionCallExpr(location, expression_list().toAst().toMutableList(), toPosition())
|
||||
FunctionCallExpression(location, expression_list().toAst().toMutableList(), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.InlineasmContext.toAst(): InlineAssembly {
|
||||
@@ -356,8 +321,10 @@ internal fun Prog8ANTLRParser.DirectiveContext.toAst() : Directive =
|
||||
|
||||
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())
|
||||
val oldenc = str?.old_alt_encoding?.text
|
||||
val enc = str?.encoding?.text
|
||||
if(oldenc!=null || enc !=null)
|
||||
throw SyntaxError("can't use alternate string encoding for directive arguments", toPosition())
|
||||
return DirectiveArg(str?.text?.substring(1, text.length-1), identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
|
||||
}
|
||||
|
||||
@@ -460,7 +427,7 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
|
||||
if (rangefrom!=null && rangeto!=null) {
|
||||
val defaultstep = if(rto.text == "to") 1 else -1
|
||||
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep.toDouble(), toPosition())
|
||||
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
||||
return RangeExpression(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
||||
}
|
||||
|
||||
if(childCount==3 && children[0].text=="(" && children[2].text==")")
|
||||
@@ -478,17 +445,38 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
|
||||
if(addressof()!=null)
|
||||
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
|
||||
|
||||
if(pipe!=null)
|
||||
return PipeExpression(pipesource.toAst(), pipetarget.toAst(), toPosition())
|
||||
|
||||
throw FatalAstException(text)
|
||||
}
|
||||
|
||||
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())
|
||||
val enc = this.encoding?.text
|
||||
val encoding =
|
||||
if(old_alt_encoding!=null)
|
||||
Encoding.SCREENCODES
|
||||
else if(enc!=null)
|
||||
Encoding.values().singleOrNull { it.prefix == enc }
|
||||
?: throw SyntaxError("invalid encoding", toPosition())
|
||||
else
|
||||
Encoding.PETSCII
|
||||
return CharLiteral(unescape(text.substring(1, text.length-1), toPosition())[0], encoding, 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())
|
||||
val enc = encoding?.text
|
||||
val encoding =
|
||||
if(old_alt_encoding!=null)
|
||||
Encoding.SCREENCODES
|
||||
else if(enc!=null)
|
||||
Encoding.values().singleOrNull { it.prefix == enc }
|
||||
?: throw SyntaxError("invalid encoding", toPosition())
|
||||
else
|
||||
Encoding.PETSCII
|
||||
return StringLiteralValue(unescape(text.substring(1, text.length-1), toPosition()), encoding, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
|
||||
@@ -597,18 +585,30 @@ private fun Prog8ANTLRParser.When_choiceContext.toAst(): WhenChoice {
|
||||
return WhenChoice(values?.toMutableList(), scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.VardeclContext.toAst(): VarDecl {
|
||||
private fun Prog8ANTLRParser.VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
|
||||
val options = decloptions()
|
||||
val zp = when {
|
||||
options.ZEROPAGEREQUIRE().isNotEmpty() -> ZeropageWish.REQUIRE_ZEROPAGE
|
||||
options.ZEROPAGE().isNotEmpty() -> ZeropageWish.PREFER_ZEROPAGE
|
||||
else -> ZeropageWish.DONTCARE
|
||||
}
|
||||
val shared = options.SHARED().isNotEmpty()
|
||||
return VarDecl(
|
||||
VarDeclType.VAR,
|
||||
type, VarDeclOrigin.USERCODE,
|
||||
datatype()?.toAst() ?: DataType.UNDEFINED,
|
||||
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||
zp,
|
||||
arrayindex()?.toAst(),
|
||||
varname.text,
|
||||
null,
|
||||
value,
|
||||
ARRAYSIG() != null || arrayindex() != null,
|
||||
false,
|
||||
SHARED()!=null,
|
||||
shared,
|
||||
null,
|
||||
toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.PipestmtContext.toAst(): Pipe {
|
||||
val source = this.source.toAst()
|
||||
val target = this.target.toAst()
|
||||
return Pipe(source, target, toPosition())
|
||||
}
|
||||
@@ -131,6 +131,7 @@ val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE)
|
||||
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
|
||||
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
||||
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||
val IterableDatatypes = arrayOf(
|
||||
|
||||
@@ -6,6 +6,7 @@ import prog8.ast.base.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.Encoding
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.round
|
||||
@@ -64,11 +65,11 @@ sealed class Expression: Node {
|
||||
is AddressOf -> {
|
||||
(other is AddressOf && other.identifier.nameInSource == identifier.nameInSource)
|
||||
}
|
||||
is RangeExpr -> {
|
||||
(other is RangeExpr && other.from==from && other.to==to && other.step==step)
|
||||
is RangeExpression -> {
|
||||
(other is RangeExpression && other.from==from && other.to==to && other.step==step)
|
||||
}
|
||||
is FunctionCallExpr -> {
|
||||
(other is FunctionCallExpr && other.target.nameInSource == target.nameInSource
|
||||
is FunctionCallExpression -> {
|
||||
(other is FunctionCallExpression && other.target.nameInSource == target.nameInSource
|
||||
&& other.args.size == args.size
|
||||
&& other.args.zip(args).all { it.first isSameAs it.second } )
|
||||
}
|
||||
@@ -204,7 +205,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
commonDatatype(
|
||||
leftDt.getOr(DataType.BYTE),
|
||||
rightDt.getOr(DataType.BYTE),
|
||||
null, "", null
|
||||
null, null
|
||||
).first
|
||||
)
|
||||
} catch (x: FatalAstException) {
|
||||
@@ -227,7 +228,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
|
||||
companion object {
|
||||
fun commonDatatype(leftDt: DataType, rightDt: DataType,
|
||||
left: Expression?, operator: String, right: Expression?): Pair<DataType, Expression?> {
|
||||
left: Expression?, right: Expression?): Pair<DataType, Expression?> {
|
||||
// byte + byte -> byte
|
||||
// byte + word -> word
|
||||
// word + byte -> word
|
||||
@@ -431,7 +432,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
else {
|
||||
val rounded = round(numbervalue)
|
||||
if(rounded != numbervalue)
|
||||
throw ExpressionError("refused silent rounding of float to avoid loss of precision", position)
|
||||
throw ExpressionError("refused rounding of float to avoid loss of precision", position)
|
||||
rounded
|
||||
}
|
||||
}
|
||||
@@ -569,14 +570,18 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if (targettype == DataType.BYTE && number >= -128 && number <=127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if (targettype == DataType.UBYTE && number >=0 && number <= 255)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if (targettype == DataType.WORD && number >= -32768 && number <= 32767)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if (targettype == DataType.UWORD && number >=0 && number <= 65535)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
try {
|
||||
if (targettype == DataType.BYTE && number >= -128 && number <= 127)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if (targettype == DataType.UBYTE && number >= 0 && number <= 255)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if (targettype == DataType.WORD && number >= -32768 && number <= 32767)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
if (targettype == DataType.UWORD && number >= 0 && number <= 65535)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number, position))
|
||||
} catch (x: ExpressionError) {
|
||||
return CastValue(false, null)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@@ -585,8 +590,8 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
}
|
||||
|
||||
class CharLiteral(val value: Char,
|
||||
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
||||
override val position: Position) : Expression() {
|
||||
val encoding: Encoding,
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -599,10 +604,10 @@ class CharLiteral(val value: Char,
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun copy() = CharLiteral(value, altEncoding, position)
|
||||
override fun copy() = CharLiteral(value, encoding, position)
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = false
|
||||
override fun constValue(program: Program): NumericLiteralValue {
|
||||
val bytevalue = program.encoding.encodeString(value.toString(), altEncoding).single()
|
||||
val bytevalue = program.encoding.encodeString(value.toString(), encoding).single()
|
||||
return NumericLiteralValue(DataType.UBYTE, bytevalue.toDouble(), position)
|
||||
}
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
@@ -611,16 +616,16 @@ class CharLiteral(val value: Char,
|
||||
override fun toString(): String = "'${escape(value.toString())}'"
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UBYTE)
|
||||
operator fun compareTo(other: CharLiteral): Int = value.compareTo(other.value)
|
||||
override fun hashCode(): Int = Objects.hash(value, altEncoding)
|
||||
override fun hashCode(): Int = Objects.hash(value, encoding)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null || other !is CharLiteral)
|
||||
return false
|
||||
return value == other.value && altEncoding == other.altEncoding
|
||||
return value == other.value && encoding == other.encoding
|
||||
}
|
||||
}
|
||||
|
||||
class StringLiteralValue(val value: String,
|
||||
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
||||
val encoding: Encoding,
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@@ -629,7 +634,7 @@ class StringLiteralValue(val value: String,
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
override fun copy() = StringLiteralValue(value, altEncoding, position)
|
||||
override fun copy() = StringLiteralValue(value, encoding, position)
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
@@ -643,11 +648,11 @@ class StringLiteralValue(val value: String,
|
||||
override fun toString(): String = "'${escape(value)}'"
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.STR)
|
||||
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
|
||||
override fun hashCode(): Int = Objects.hash(value, altEncoding)
|
||||
override fun hashCode(): Int = Objects.hash(value, encoding)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is StringLiteralValue)
|
||||
return false
|
||||
return value==other.value && altEncoding == other.altEncoding
|
||||
return value==other.value && encoding == other.encoding
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,10 +752,10 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
}
|
||||
}
|
||||
|
||||
class RangeExpr(var from: Expression,
|
||||
var to: Expression,
|
||||
var step: Expression,
|
||||
override val position: Position) : Expression() {
|
||||
class RangeExpression(var from: Expression,
|
||||
var to: Expression,
|
||||
var step: Expression,
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -773,7 +778,7 @@ class RangeExpr(var from: Expression,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = RangeExpr(from.copy(), to.copy(), step.copy(), position)
|
||||
override fun copy() = RangeExpression(from.copy(), to.copy(), step.copy(), position)
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@@ -898,7 +903,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
|
||||
fun wasStringLiteral(program: Program): Boolean {
|
||||
val decl = targetVarDecl(program)
|
||||
if(decl == null || !decl.autogeneratedDontRemove)
|
||||
if(decl == null || decl.origin!=VarDeclOrigin.STRINGLITERAL)
|
||||
return false
|
||||
|
||||
val scope=decl.definingModule
|
||||
@@ -906,9 +911,9 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionCallExpr(override var target: IdentifierReference,
|
||||
override var args: MutableList<Expression>,
|
||||
override val position: Position) : Expression(), IFunctionCall {
|
||||
class FunctionCallExpression(override var target: IdentifierReference,
|
||||
override var args: MutableList<Expression>,
|
||||
override val position: Position) : Expression(), IFunctionCall {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -917,7 +922,7 @@ class FunctionCallExpr(override var target: IdentifierReference,
|
||||
args.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun copy() = FunctionCallExpr(target.copy(), args.map { it.copy() }.toMutableList(), position)
|
||||
override fun copy() = FunctionCallExpression(target.copy(), args.map { it.copy() }.toMutableList(), position)
|
||||
override val isSimple = target.nameInSource.size==1 && (target.nameInSource[0] in arrayOf("msb", "lsb", "peek", "peekw"))
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
@@ -1011,8 +1016,8 @@ class ContainmentCheck(var element: Expression,
|
||||
val exists = (iterable as ArrayLiteralValue).value.any { it.constValue(program)==elementConst }
|
||||
return NumericLiteralValue.fromBoolean(exists, position)
|
||||
}
|
||||
is RangeExpr -> {
|
||||
val intRange = (iterable as RangeExpr).toConstantIntegerRange()
|
||||
is RangeExpression -> {
|
||||
val intRange = (iterable as RangeExpression).toConstantIntegerRange()
|
||||
if(intRange!=null && elementConst.type in IntegerDatatypes) {
|
||||
val exists = elementConst.number.toInt() in intRange
|
||||
return NumericLiteralValue.fromBoolean(exists, position)
|
||||
@@ -1021,7 +1026,7 @@ class ContainmentCheck(var element: Expression,
|
||||
is StringLiteralValue -> {
|
||||
if(elementConst.type in ByteDatatypes) {
|
||||
val stringval = iterable as StringLiteralValue
|
||||
val exists = program.encoding.encodeString(stringval.value, stringval.altEncoding).contains(elementConst.number.toInt().toUByte() )
|
||||
val exists = program.encoding.encodeString(stringval.value, stringval.encoding).contains(elementConst.number.toInt().toUByte() )
|
||||
return NumericLiteralValue.fromBoolean(exists, position)
|
||||
}
|
||||
}
|
||||
@@ -1035,8 +1040,8 @@ class ContainmentCheck(var element: Expression,
|
||||
if(array.value.isEmpty())
|
||||
return NumericLiteralValue.fromBoolean(false, position)
|
||||
}
|
||||
is RangeExpr -> {
|
||||
val size = (iterable as RangeExpr).size()
|
||||
is RangeExpression -> {
|
||||
val size = (iterable as RangeExpression).size()
|
||||
if(size!=null && size==0)
|
||||
return NumericLiteralValue.fromBoolean(false, position)
|
||||
}
|
||||
@@ -1072,6 +1077,52 @@ class ContainmentCheck(var element: Expression,
|
||||
}
|
||||
}
|
||||
|
||||
class PipeExpression(val expressions: MutableList<Expression>, override val position: Position): Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
constructor(source: Expression, target: Expression, position: Position) : this(mutableListOf(), position) {
|
||||
if(source is PipeExpression) {
|
||||
expressions.addAll(source.expressions)
|
||||
expressions.add(target)
|
||||
} else {
|
||||
expressions.add(source)
|
||||
expressions.add(target)
|
||||
}
|
||||
}
|
||||
|
||||
override val isSimple = false
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
expressions.forEach { it.linkParents(this) }
|
||||
}
|
||||
override fun copy(): PipeExpression = PipeExpression(expressions.map {it.copy()}.toMutableList(), position)
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun referencesIdentifier(nameInSource: List<String>) =
|
||||
expressions.any { it.referencesIdentifier(nameInSource) }
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val last = expressions.last()
|
||||
val type = last.inferType(program)
|
||||
if(type.isKnown)
|
||||
return type
|
||||
val identifier = last as? IdentifierReference
|
||||
if(identifier!=null) {
|
||||
val call = FunctionCallExpression(identifier, mutableListOf(), identifier.position)
|
||||
return call.inferType(program)
|
||||
}
|
||||
return InferredTypes.InferredType.unknown()
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Expression)
|
||||
require(replacement is Expression)
|
||||
val idx = expressions.indexOf(node)
|
||||
expressions[idx] = replacement
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun invertCondition(cond: Expression): BinaryExpression? {
|
||||
if(cond is BinaryExpression) {
|
||||
|
||||
@@ -171,17 +171,25 @@ enum class ZeropageWish {
|
||||
NOT_IN_ZEROPAGE
|
||||
}
|
||||
|
||||
enum class VarDeclOrigin {
|
||||
USERCODE,
|
||||
SUBROUTINEPARAM,
|
||||
STRINGLITERAL,
|
||||
ARRAYLITERAL
|
||||
}
|
||||
|
||||
|
||||
class VarDecl(val type: VarDeclType,
|
||||
private val declaredDatatype: DataType,
|
||||
var zeropage: ZeropageWish,
|
||||
var arraysize: ArrayIndex?,
|
||||
override val name: String,
|
||||
var value: Expression?,
|
||||
val isArray: Boolean,
|
||||
val autogeneratedDontRemove: Boolean,
|
||||
val sharedWithAsm: Boolean,
|
||||
val subroutineParameter: SubroutineParameter?,
|
||||
override val position: Position) : Statement(), INamedStatement {
|
||||
val origin: VarDeclOrigin,
|
||||
private val declaredDatatype: DataType,
|
||||
var zeropage: ZeropageWish,
|
||||
var arraysize: ArrayIndex?,
|
||||
override val name: String,
|
||||
var value: Expression?,
|
||||
val isArray: Boolean,
|
||||
val sharedWithAsm: Boolean,
|
||||
val subroutineParameter: SubroutineParameter?,
|
||||
override val position: Position) : Statement(), INamedStatement {
|
||||
override lateinit var parent: Node
|
||||
var allowInitializeWithZero = true
|
||||
|
||||
@@ -191,9 +199,8 @@ class VarDecl(val type: VarDeclType,
|
||||
private var autoHeapValueSequenceNumber = 0
|
||||
|
||||
fun fromParameter(param: SubroutineParameter): VarDecl {
|
||||
return VarDecl(VarDeclType.VAR, param.type, ZeropageWish.DONTCARE, null, param.name, null,
|
||||
return VarDecl(VarDeclType.VAR, VarDeclOrigin.SUBROUTINEPARAM, param.type, ZeropageWish.DONTCARE, null, param.name, null,
|
||||
isArray = false,
|
||||
autogeneratedDontRemove = true,
|
||||
sharedWithAsm = false,
|
||||
subroutineParameter = param,
|
||||
position = param.position
|
||||
@@ -205,8 +212,8 @@ class VarDecl(val type: VarDeclType,
|
||||
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,
|
||||
isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, subroutineParameter = null, position = array.position)
|
||||
return VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array,
|
||||
isArray = true, sharedWithAsm = false, subroutineParameter = null, position = array.position)
|
||||
}
|
||||
|
||||
fun defaultZero(dt: DataType, position: Position) = when(dt) {
|
||||
@@ -260,11 +267,17 @@ class VarDecl(val type: VarDeclType,
|
||||
}
|
||||
|
||||
override fun copy(): VarDecl {
|
||||
val c = VarDecl(type, declaredDatatype, zeropage, arraysize?.copy(), name, value?.copy(),
|
||||
isArray, autogeneratedDontRemove, sharedWithAsm, subroutineParameter, position)
|
||||
c.allowInitializeWithZero = this.allowInitializeWithZero
|
||||
return c
|
||||
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), name, value?.copy(),
|
||||
isArray, sharedWithAsm, subroutineParameter, position)
|
||||
copy.allowInitializeWithZero = this.allowInitializeWithZero
|
||||
return copy
|
||||
}
|
||||
|
||||
fun findInitializer(program: Program): Assignment? =
|
||||
(parent as IStatementContainer).statements
|
||||
.asSequence()
|
||||
.filterIsInstance<Assignment>()
|
||||
.singleOrNull { it.origin==AssignmentOrigin.VARINIT && it.target.identifier?.targetVarDecl(program) === this }
|
||||
}
|
||||
|
||||
class ArrayIndex(var indexExpr: Expression,
|
||||
@@ -299,7 +312,16 @@ class ArrayIndex(var indexExpr: Expression,
|
||||
override fun copy() = ArrayIndex(indexExpr.copy(), position)
|
||||
}
|
||||
|
||||
class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
|
||||
enum class AssignmentOrigin {
|
||||
USERCODE,
|
||||
VARINIT,
|
||||
PARAMETERASSIGN,
|
||||
OPTIMIZER,
|
||||
BEFOREASMGEN,
|
||||
ASMGEN,
|
||||
}
|
||||
|
||||
class Assignment(var target: AssignTarget, var value: Expression, var origin: AssignmentOrigin, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -317,7 +339,7 @@ class Assignment(var target: AssignTarget, var value: Expression, override val p
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy()= Assignment(target.copy(), value.copy(), position)
|
||||
override fun copy()= Assignment(target.copy(), value.copy(), origin, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun toString() = "Assignment(target: $target, value: $value, pos=$position)"
|
||||
@@ -383,6 +405,12 @@ class Assignment(var target: AssignTarget, var value: Expression, override val p
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun initializerFor(program: Program) =
|
||||
if(origin==AssignmentOrigin.VARINIT)
|
||||
target.identifier!!.targetVarDecl(program)
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
data class AssignTarget(var identifier: IdentifierReference?,
|
||||
@@ -1014,3 +1042,36 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun copy() = DirectMemoryWrite(addressExpression.copy(), position)
|
||||
}
|
||||
|
||||
|
||||
class Pipe(val expressions: MutableList<Expression>, override val position: Position): Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
constructor(source: Expression, target: Expression, position: Position) : this(mutableListOf(), position) {
|
||||
if(source is PipeExpression)
|
||||
expressions.addAll(source.expressions)
|
||||
else
|
||||
expressions.add(source)
|
||||
|
||||
if(target is PipeExpression)
|
||||
expressions.addAll(target.expressions)
|
||||
else
|
||||
expressions.add(target)
|
||||
}
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
expressions.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun copy() = Pipe(expressions.map { it.copy() }.toMutableList(), position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Expression)
|
||||
require(replacement is Expression)
|
||||
val idx = expressions.indexOf(node)
|
||||
expressions[idx] = replacement
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ abstract class AstWalker {
|
||||
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@@ -109,7 +109,7 @@ abstract class AstWalker {
|
||||
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(program: Program): Iterable<IAstModification> = noModifications
|
||||
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(range: RangeExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@@ -120,6 +120,8 @@ abstract class AstWalker {
|
||||
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(whenStmt: When, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(pipe: Pipe, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
|
||||
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@@ -137,7 +139,7 @@ abstract class AstWalker {
|
||||
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@@ -152,7 +154,7 @@ abstract class AstWalker {
|
||||
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(program: Program): Iterable<IAstModification> = noModifications
|
||||
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@@ -163,6 +165,8 @@ abstract class AstWalker {
|
||||
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(whenStmt: When, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(pipe: Pipe, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
|
||||
protected val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
|
||||
|
||||
@@ -256,7 +260,7 @@ abstract class AstWalker {
|
||||
track(after(subroutine, parent), subroutine, parent)
|
||||
}
|
||||
|
||||
fun visit(functionCallExpr: FunctionCallExpr, parent: Node) {
|
||||
fun visit(functionCallExpr: FunctionCallExpression, parent: Node) {
|
||||
track(before(functionCallExpr, parent), functionCallExpr, parent)
|
||||
functionCallExpr.target.accept(this, functionCallExpr)
|
||||
functionCallExpr.args.forEach { it.accept(this, functionCallExpr) }
|
||||
@@ -302,7 +306,7 @@ abstract class AstWalker {
|
||||
track(after(branch, parent), branch, parent)
|
||||
}
|
||||
|
||||
fun visit(range: RangeExpr, parent: Node) {
|
||||
fun visit(range: RangeExpression, parent: Node) {
|
||||
track(before(range, parent), range, parent)
|
||||
range.from.accept(this, range)
|
||||
range.to.accept(this, range)
|
||||
@@ -457,5 +461,17 @@ abstract class AstWalker {
|
||||
whenChoice.statements.accept(this, whenChoice)
|
||||
track(after(whenChoice, parent), whenChoice, parent)
|
||||
}
|
||||
|
||||
fun visit(pipe: Pipe, parent: Node) {
|
||||
track(before(pipe, parent), pipe, parent)
|
||||
pipe.expressions.forEach { it.accept(this, pipe) }
|
||||
track(after(pipe, parent), pipe, parent)
|
||||
}
|
||||
|
||||
fun visit(pipe: PipeExpression, parent: Node) {
|
||||
track(before(pipe, parent), pipe, parent)
|
||||
pipe.expressions.forEach { it.accept(this, pipe) }
|
||||
track(after(pipe, parent), pipe, parent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ interface IAstVisitor {
|
||||
subroutine.statements.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(functionCallExpr: FunctionCallExpr) {
|
||||
fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
functionCallExpr.target.accept(this)
|
||||
functionCallExpr.args.forEach { it.accept(this) }
|
||||
}
|
||||
@@ -76,7 +76,7 @@ interface IAstVisitor {
|
||||
branch.elsepart.accept(this)
|
||||
}
|
||||
|
||||
fun visit(range: RangeExpr) {
|
||||
fun visit(range: RangeExpression) {
|
||||
range.from.accept(this)
|
||||
range.to.accept(this)
|
||||
range.step.accept(this)
|
||||
@@ -181,4 +181,12 @@ interface IAstVisitor {
|
||||
whenChoice.values?.forEach { it.accept(this) }
|
||||
whenChoice.statements.accept(this)
|
||||
}
|
||||
|
||||
fun visit(pipe: Pipe) {
|
||||
pipe.expressions.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(pipe: PipeExpression) {
|
||||
pipe.expressions.forEach { it.accept(this) }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
package prog8.compilerinterface
|
||||
|
||||
enum class Encoding(val prefix: String) {
|
||||
PETSCII("petscii"), // c64/c128/cx16
|
||||
SCREENCODES("sc"), // c64/c128/cx16
|
||||
ISO("iso") // cx16
|
||||
}
|
||||
|
||||
interface IStringEncoding {
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<UByte>
|
||||
fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String
|
||||
fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||
fun decodeString(bytes: List<UByte>, encoding: Encoding): String
|
||||
}
|
||||
@@ -26,6 +26,7 @@ compileTestKotlin {
|
||||
dependencies {
|
||||
implementation project(':compilerAst')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
}
|
||||
|
||||
|
||||
@@ -11,5 +11,6 @@
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -141,6 +141,7 @@ private val functionSignatures: List<FSignature> = listOf(
|
||||
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
FSignature("pokemon" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||
FSignature("pop" , false, listOf(FParam("target", ByteDatatypes)), null),
|
||||
FSignature("popw" , false, listOf(FParam("target", WordDatatypes)), null),
|
||||
|
||||
@@ -5,9 +5,7 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.FunctionCallExpr
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
|
||||
@@ -25,6 +23,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
visit(program)
|
||||
}
|
||||
|
||||
val allIdentifiers: Map<Pair<IdentifierReference, Position>, Statement> = allIdentifiersAndTargets
|
||||
|
||||
private val usedSubroutines: Set<Subroutine> by lazy {
|
||||
calledBy.keys + program.entrypoint
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
super.visit(directive)
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) {
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
val otherSub = functionCallExpr.target.targetSubroutine(program)
|
||||
if (otherSub != null) {
|
||||
functionCallExpr.definingSubroutine?.let { thisSub ->
|
||||
@@ -122,6 +122,30 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
allAssemblyNodes.add(inlineAssembly)
|
||||
}
|
||||
|
||||
override fun visit(pipe: PipeExpression) {
|
||||
processPipe(pipe.expressions, pipe)
|
||||
super.visit(pipe)
|
||||
}
|
||||
|
||||
override fun visit(pipe: Pipe) {
|
||||
processPipe(pipe.expressions, pipe)
|
||||
super.visit(pipe)
|
||||
}
|
||||
|
||||
private fun processPipe(expressions: Iterable<Expression>, pipe: Node) {
|
||||
expressions.forEach {
|
||||
if(it is IdentifierReference){
|
||||
val otherSub = it.targetSubroutine(program)
|
||||
if(otherSub!=null) {
|
||||
pipe.definingSubroutine?.let { thisSub ->
|
||||
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + pipe
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkRecursiveCalls(errors: IErrorReporter) {
|
||||
val cycles = recursionCycles()
|
||||
if(cycles.any()) {
|
||||
|
||||
@@ -4,10 +4,6 @@ interface IAssemblyGenerator {
|
||||
fun compileToAssembly(): IAssemblyProgram
|
||||
}
|
||||
|
||||
const val generatedLabelPrefix = "_prog8_label_"
|
||||
const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
|
||||
const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
|
||||
|
||||
interface IAssemblyProgram {
|
||||
val valid: Boolean
|
||||
val name: String
|
||||
|
||||
@@ -4,11 +4,14 @@ import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
|
||||
|
||||
// TODO list of supported string encodings
|
||||
|
||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
val name: String
|
||||
val machine: IMachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte>
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> // TODO use Result
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String // TODO use Result
|
||||
|
||||
fun asmsubArgsEvalOrder(sub: Subroutine): List<Int>
|
||||
fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
|
||||
@@ -33,5 +33,4 @@ 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>>
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package prog8.compilerinterface
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import prog8.ast.base.*
|
||||
|
||||
|
||||
class ZeropageDepletedError(message: String) : Exception(message)
|
||||
class ZeropageAllocationError(message: String) : Exception(message)
|
||||
|
||||
|
||||
abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
@@ -14,10 +17,12 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
|
||||
|
||||
|
||||
private val allocations = mutableMapOf<UInt, Pair<String, DataType>>()
|
||||
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
// the variables allocated into Zeropage.
|
||||
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
||||
protected val allocatedVariables = mutableMapOf<List<String>, Pair<UInt, Pair<DataType, Int>>>()
|
||||
private val allocations = mutableMapOf<UInt, Pair<List<String>, DataType>>()
|
||||
|
||||
val allowedDatatypes = NumericDatatypes
|
||||
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
fun removeReservedFromFreePool() {
|
||||
for (reserved in options.zpReserved)
|
||||
@@ -35,57 +40,71 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
return free.windowed(2).any { it[0] == it[1] - 1u }
|
||||
}
|
||||
|
||||
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): UInt {
|
||||
require(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
|
||||
fun allocate(name: List<String>, datatype: DataType, arraySize: Int?, position: Position?, errors: IErrorReporter): Result<Pair<UInt, Int>, ZeropageAllocationError> {
|
||||
require(name.isEmpty() || !allocations.values.any { it.first==name } ) {"name can't be allocated twice"}
|
||||
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
throw InternalCompilerException("zero page usage has been disabled")
|
||||
return Err(ZeropageAllocationError("zero page usage has been disabled"))
|
||||
|
||||
val size: Int =
|
||||
when (datatype) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
|
||||
DataType.STR, in ArrayDatatypes -> {
|
||||
val memsize = arraySize!! * options.compTarget.memorySize(ArrayToElementTypes.getValue(datatype))
|
||||
if(position!=null)
|
||||
errors.warn("allocating a large value in zeropage; str/array $memsize bytes", position)
|
||||
else
|
||||
errors.warn("$name: allocating a large value in zeropage; str/array $memsize bytes", Position.DUMMY)
|
||||
memsize
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if (options.floats) {
|
||||
val memsize = options.compTarget.memorySize(DataType.FLOAT)
|
||||
if(position!=null)
|
||||
errors.warn("allocated a large value (float) in zeropage", position)
|
||||
errors.warn("allocating a large value in zeropage; float $memsize bytes", position)
|
||||
else
|
||||
errors.warn("$scopedname: allocated a large value (float) in zeropage", Position.DUMMY)
|
||||
options.compTarget.machine.FLOAT_MEM_SIZE
|
||||
} else throw InternalCompilerException("floating point option not enabled")
|
||||
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
|
||||
memsize
|
||||
} else return Err(ZeropageAllocationError("floating point option not enabled"))
|
||||
}
|
||||
else -> throw InternalCompilerException("cannot put datatype $datatype in zeropage")
|
||||
else -> return Err(ZeropageAllocationError("cannot put datatype $datatype in zeropage"))
|
||||
}
|
||||
|
||||
if(free.size > 0) {
|
||||
if(size==1) {
|
||||
synchronized(this) {
|
||||
if(free.size > 0) {
|
||||
if(size==1) {
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if(oneSeparateByteFree(candidate))
|
||||
return Ok(Pair(makeAllocation(candidate, 1, datatype, name), 1))
|
||||
}
|
||||
return Ok(Pair(makeAllocation(free[0], 1, datatype, name), 1))
|
||||
}
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if(loneByte(candidate))
|
||||
return makeAllocation(candidate, 1, datatype, scopedname)
|
||||
if (sequentialFree(candidate, size))
|
||||
return Ok(Pair(makeAllocation(candidate, size, datatype, name), size))
|
||||
}
|
||||
return makeAllocation(free[0], 1, datatype, scopedname)
|
||||
}
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if (sequentialFree(candidate, size))
|
||||
return makeAllocation(candidate, size, datatype, scopedname)
|
||||
}
|
||||
}
|
||||
|
||||
throw ZeropageDepletedError("ERROR: no free space in ZP to allocate $size sequential bytes")
|
||||
return Err(ZeropageAllocationError("no more free space in ZP to allocate $size sequential bytes"))
|
||||
}
|
||||
|
||||
protected fun reserve(range: UIntRange) = free.removeAll(range)
|
||||
private fun reserve(range: UIntRange) = free.removeAll(range)
|
||||
|
||||
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: String?): UInt {
|
||||
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: List<String>): UInt {
|
||||
require(size>=0)
|
||||
free.removeAll(address until address+size.toUInt())
|
||||
allocations[address] = (name ?: "<unnamed>") to datatype
|
||||
allocations[address] = name to datatype
|
||||
if(name.isNotEmpty())
|
||||
allocatedVariables[name] = address to (datatype to size)
|
||||
return address
|
||||
}
|
||||
|
||||
private fun loneByte(address: UInt) = address in free && address-1u !in free && address+1u !in free
|
||||
private fun oneSeparateByteFree(address: UInt) = address in free && address-1u !in free && address+1u !in free
|
||||
private fun sequentialFree(address: UInt, size: Int): Boolean {
|
||||
require(size>0)
|
||||
return free.containsAll((address until address+size.toUInt()).toList())
|
||||
}
|
||||
|
||||
fun allocatedZeropageVariable(name: List<String>): Pair<UInt, Pair<DataType, Int>>? = allocatedVariables[name]
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.9 (py3)" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.10 (py3)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -58,6 +58,7 @@ Language features
|
||||
- Conditional branches to map directly on processor branch instructions
|
||||
- ``when`` statement to avoid if-else chains
|
||||
- ``in`` expression for concise and efficient multi-value/containment test
|
||||
- pipe operator ``|>`` to rewrite nested function call expressions in a more readable chained form
|
||||
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
|
||||
- Variable data types include signed and unsigned bytes and words, arrays, strings.
|
||||
- Floating point math also supported if the target system provides floating point library routines (C64 and Cx16 both do).
|
||||
|
||||
@@ -36,10 +36,10 @@ RAM, ROM, I/O
|
||||
#. what part(s) of the address space is memory mapped I/O registers?
|
||||
#. is there a banking system? How does it work (how do you select Ram/Rom banks)? How is the default bank configuration set?
|
||||
|
||||
Screen and Character encodings
|
||||
------------------------------
|
||||
Character encodings
|
||||
-------------------
|
||||
#. provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
|
||||
#. provide alternate character encoding table (if any)
|
||||
#. provide alternate character encodings (if any)
|
||||
#. what are the system's character screen dimensions?
|
||||
#. is there a screen matrix directly accessible in Ram? Provide addresses of the character matrix and color attributes matrix, if any.
|
||||
|
||||
|
||||
+54
-12
@@ -193,19 +193,26 @@ Values will usually be part of an expression or assignment statement::
|
||||
-33.456e52 ; floating point number
|
||||
"Hi, I am a string" ; text string, encoded with compiler target default encoding
|
||||
'a' ; byte value (ubyte) for the letter a
|
||||
@"Alternate" ; text string, encoded with alternate encoding
|
||||
@'a' ; byte value of the letter a, using alternate encoding
|
||||
@"Alternate" ; text string, encoded with alternate encoding (old deprecated syntax)
|
||||
sc:"Alternate" ; text string, encoded with c64 screencode encoding (current syntax)
|
||||
sc:'a' ; byte value of the letter a in c64 screencode encoding
|
||||
|
||||
byte counter = 42 ; variable of size 8 bits, with initial value 42
|
||||
|
||||
|
||||
*zeropage tag:*
|
||||
*putting a variable in zeropage:*
|
||||
If you add the ``@zp`` tag to the variable declaration, the compiler will prioritize this variable
|
||||
when selecting variables to put into zero page. If there are enough free locations in the zeropage,
|
||||
it will then try to fill it with as much other variables as possible (before they will be put in regular memory pages).
|
||||
when selecting variables to put into zero page (but no guarantees). If there are enough free locations in the zeropage,
|
||||
it will try to fill it with as much other variables as possible (before they will be put in regular memory pages).
|
||||
Use ``@requirezp`` tag to *force* the variable into zeropage, but if there is no more free space the compilation will fail.
|
||||
It's possible to put strings, arrays and floats into zeropage too, however because Zp space is really scarce
|
||||
this is not advised as they will eat up the available space very quickly. It's best to only put byte or word
|
||||
variables in Zeropage.
|
||||
|
||||
Example::
|
||||
|
||||
byte @zp zeropageCounter = 42
|
||||
byte @zp smallcounter = 42
|
||||
uword @requirezp zppointer = $4000
|
||||
|
||||
|
||||
*shared tag:*
|
||||
@@ -308,12 +315,28 @@ Strings
|
||||
Strings are a sequence of characters enclosed in ``"`` quotes. The length is limited to 255 characters.
|
||||
They're stored and treated much the same as a byte array,
|
||||
but they have some special properties because they are considered to be *text*.
|
||||
Strings in your source code files will be encoded (translated from ASCII/UTF-8) into bytes via the
|
||||
default encoding that is used on the target platform. For the C-64, this is CBM PETSCII.
|
||||
Alternate-encoding strings (prefixed with ``@``) will be encoded via the alternate encoding for the
|
||||
platform (if defined). For the C-64, that is SCREEN CODES (also known as POKE codes).
|
||||
This @-prefix can also be used for character byte values.
|
||||
Strings (without encoding prefix) will be encoded (translated from ASCII/UTF-8) into bytes via the
|
||||
*default encoding* for the target platform. On the CBM machines, this is CBM PETSCII.
|
||||
|
||||
.. sidebar::
|
||||
Deprecated ``@`` prefix
|
||||
|
||||
In older versions of the language, the ``@`` prefix was used to specify the
|
||||
CBM screencode encoding. This syntax is still supported for now, but will be removed
|
||||
in a future language version.
|
||||
|
||||
Alternative encodings can be specified with a ``encodingname:`` prefix to the string or character literal.
|
||||
The following encodings are currently recognised:
|
||||
|
||||
- ``petscii`` Petscii, the default encoding on CBM machines (c64, c128, cx16)
|
||||
- ``sc`` CBM-screencodes aka 'poke' codes (c64, c128, cx16)
|
||||
- ``iso`` iso-8859-15 text (supported on cx16)
|
||||
|
||||
So the following is a string literal that will be encoded into memory bytes using the iso encoding.
|
||||
It can be correctly displayed on the screen only if a iso-8859-15 charset has been activated first
|
||||
(the Commander X16 has this feature built in)::
|
||||
|
||||
iso:"Käse, Straße"
|
||||
|
||||
You can concatenate two string literals using '+', which can be useful to
|
||||
split long strings over separate lines. But remember that the length
|
||||
@@ -599,9 +622,15 @@ You can also reference idendifiers defined elsewhere in your code.
|
||||
|
||||
Read the :ref:`syntaxreference` chapter for all details on the available operators and kinds of expressions you can write.
|
||||
|
||||
.. note::
|
||||
**Order of evaluation:**
|
||||
|
||||
The order of evaluation of expression operands is *unspecified* and should not be relied upon.
|
||||
There is no guarantee of a left-to-right or right-to-left evaluation. But don't confuse this with
|
||||
operator precedence order (multiplication comes before addition etcetera).
|
||||
|
||||
.. attention::
|
||||
**Floating points used in expressions:**
|
||||
**Floating point values used in expressions:**
|
||||
|
||||
When a floating point value is used in a calculation, the result will be a floating point, and byte or word values
|
||||
will be automatically converted into floats in this case. The compiler will issue a warning though when this happens, because floating
|
||||
@@ -679,6 +708,15 @@ The arguments in parentheses after the function name, should match the parameter
|
||||
If you want to ignore a return value of a subroutine, you should prefix the call with the ``void`` keyword.
|
||||
Otherwise the compiler will issue a warning about discarding a result value.
|
||||
|
||||
Deeply nested function calls can be rewritten as a chain using the *pipe operator* ``|>`` as long as they
|
||||
are unary functions (taking a single argument).
|
||||
|
||||
.. note::
|
||||
**Order of evaluation:**
|
||||
|
||||
The order of evaluation of arguments is *unspecified* and should not be relied upon.
|
||||
There is no guarantee of a left-to-right or right-to-left evaluation of the call arguments.
|
||||
|
||||
.. caution::
|
||||
Note that due to the way parameters are processed by the compiler,
|
||||
subroutines are *non-reentrant*. This means you cannot create recursive calls.
|
||||
@@ -881,6 +919,10 @@ poke(address, value)
|
||||
pokew(address, value)
|
||||
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
|
||||
|
||||
pokemon(address, value)
|
||||
Attempts to write a byte to a ROM at a location in machine language monitor bank.
|
||||
Doesn't have anything to do with a certain video game.
|
||||
|
||||
push(value)
|
||||
pushes a byte value on the CPU hardware stack. Lowlevel function that should normally not be used.
|
||||
|
||||
|
||||
@@ -266,12 +266,15 @@ Variables should be declared with their exact type and size so the compiler can
|
||||
for them. You can give them an initial value as well. That value can be a simple literal value,
|
||||
or an expression. If you don't provide an intial value yourself, zero will be used.
|
||||
You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
|
||||
when selecting variables to be put into zeropage.
|
||||
when selecting variables to be put into zeropage (but no guarantees). If the ZP is full,
|
||||
the variable will be allocated in normal memory elsewhere.
|
||||
Use the ``@requirezp`` tag to force the variable in zeropage, but if the ZP is full,
|
||||
the compilation will fail.
|
||||
You can add a ``@shared`` shared-tag, to tell the compiler that the variable is shared
|
||||
with some assembly code and that it should not be optimized away if not used elsewhere.
|
||||
The syntax is::
|
||||
|
||||
<datatype> [ @shared ] [ @zp ] <variable name> [ = <initial value> ]
|
||||
<datatype> [ @shared ] [ @zp ] [ @requirezp ] <variable name> [ = <initial value> ]
|
||||
|
||||
Various examples::
|
||||
|
||||
@@ -279,14 +282,14 @@ Various examples::
|
||||
byte counter = len([1, 2, 3]) * 20
|
||||
byte age = 2018 - 1974
|
||||
float wallet = 55.25
|
||||
str name = "my name is Irmen"
|
||||
str name = @"my name is Irmen" ; string with alternative byte encoding
|
||||
str name = "my name is Alice"
|
||||
uword address = &counter
|
||||
byte[] values = [11, 22, 33, 44, 55]
|
||||
byte[5] values ; array of 5 bytes, initially set to zero
|
||||
byte[5] values = 255 ; initialize with five 255 bytes
|
||||
|
||||
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
||||
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
||||
uword @requirezp zpaddr = $3000 ; we require this variable in Zeropage
|
||||
word @shared asmvar ; variable is used in assembly code but not elsewhere
|
||||
|
||||
|
||||
@@ -428,10 +431,23 @@ memory at the given index. See :ref:`pointervars`
|
||||
|
||||
String
|
||||
^^^^^^
|
||||
.. sidebar::
|
||||
Deprecated ``@`` prefix
|
||||
|
||||
``"hello"`` is a string translated into the default character encoding (PETSCII)
|
||||
In older versions of the language, the ``@`` prefix was used to specify the
|
||||
CBM screencode encoding. This syntax is still supported for now, but will be removed
|
||||
in a future language version.
|
||||
|
||||
A string literal can occur with or without an encoding prefix (encoding followed by ':' followed by the string itself).
|
||||
When this is omitted, the string is stored in the machine's default character encoding (which is PETSCII on the CBM machines).
|
||||
You can choose to store the string in other encodings such as ``sc`` (screencodes) or ``iso`` (iso-8859-15).
|
||||
Here are several examples:
|
||||
|
||||
- ``"hello"`` a string translated into the default character encoding (PETSCII)
|
||||
- ``petscii:"hello"`` same as the above, on CBM machines.
|
||||
- ``sc:"my name is Alice"`` string with screencode encoding (new syntax)
|
||||
- ``iso:"Ich heiße François"`` string in iso encoding
|
||||
|
||||
``@"hello"`` is a string translated into the alternate character encoding (Screencodes/pokes)
|
||||
|
||||
There are several escape sequences available to put special characters into your string value:
|
||||
|
||||
@@ -520,6 +536,24 @@ containment check: ``in``
|
||||
txt.print("email address seems ok")
|
||||
}
|
||||
|
||||
pipe: ``|>``
|
||||
Used as an alternative to nesting function calls. The pipe operator is used to 'pipe' the value
|
||||
into the next function. It only works on unary functions (taking a single argument), because you just
|
||||
specify the *name* of the function that the value has to be piped into. The resulting value will be piped
|
||||
into the next function as long as there are chained pipes.
|
||||
For example, this: ``txt.print_uw(add_bonus(determine_score(get_player(1))))``
|
||||
can be rewritten as::
|
||||
|
||||
get_player(1)
|
||||
|> determine_score
|
||||
|> add_bonus
|
||||
|> txt.print_uw
|
||||
|
||||
It also works for expressions that return a value, for example ``uword score = add_bonus(determine_score(get_player(1)))`` ::
|
||||
|
||||
uword score = get_player(1)
|
||||
|> determine_score
|
||||
|> add_bonus
|
||||
|
||||
address of: ``&``
|
||||
This is a prefix operator that can be applied to a string or array variable or literal value.
|
||||
@@ -787,7 +821,7 @@ where <statements> can be just a single statement for instance just a ``goto``,
|
||||
<alternative statements>
|
||||
}
|
||||
|
||||
The XX corresponds to one of the eigth branching instructions so the possibilities are:
|
||||
The XX corresponds to one of the processor's branching instructions, so the possibilities are:
|
||||
``if_cs``, ``if_cc``, ``if_eq``, ``if_ne``, ``if_pl``, ``if_mi``, ``if_vs`` and ``if_vc``.
|
||||
It can also be one of the four aliases that are easier to read: ``if_z``, ``if_nz``, ``if_pos`` and ``if_neg``.
|
||||
|
||||
|
||||
@@ -64,10 +64,11 @@ regular subroutines
|
||||
- subroutine parameters are just variables scoped to the subroutine.
|
||||
- the arguments passed in a call are evaluated (using the eval-stack if needed) and then
|
||||
copied into those variables.
|
||||
This sometimes can seem inefficient but it's required to allow subroutines to work locally
|
||||
Using variables for this sometimes can seem inefficient but it's required to allow subroutines to work locally
|
||||
with their parameters and allow them to modify them as required, without changing the
|
||||
variables used in the call's arguments. If you want to get rid of this overhead you'll
|
||||
have to make an ``asmsub`` routine in assembly instead.
|
||||
- the order of evaluation of subroutine call arguments *is unspecified* and should not be relied upon.
|
||||
- the return value is passed back to the caller via cpu register(s):
|
||||
Byte values will be put in ``A`` .
|
||||
Word values will be put in ``A`` + ``Y`` register pair.
|
||||
|
||||
+12
-10
@@ -1,9 +1,9 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
For next compiler release (7.7)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
...tbd
|
||||
For next release
|
||||
^^^^^^^^^^^^^^^^
|
||||
...
|
||||
|
||||
|
||||
Need help with
|
||||
@@ -21,10 +21,11 @@ Blocked by an official Commander-x16 r39 release
|
||||
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- pipe operator ``|>``
|
||||
- can we promise a left-to-right function call argument evaluation? without sacrificing performance
|
||||
- unify FunctioncallExpression + FunctioncallStatement and PipeExpression + Pipe statement, may require moving Expression/Statement into interfaces instead of abstract base classes
|
||||
- for the pipe operator: recognise a placeholder (``?`` or ``%`` or ``_``) in a non-unary function call to allow things as ``4 |> mkword(?, $44) |> print_uw``
|
||||
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``v_``
|
||||
then we can get rid of the instruction lists in the machinedefinitions as well?
|
||||
- fix the asm-labels problem (github issue #62)
|
||||
- make it possible to inline non-asmsub routines that just contain a single statement (return, functioncall, assignment)
|
||||
but this requires all identifiers in the inlined expression to be changed to fully scoped names
|
||||
- simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation
|
||||
@@ -51,10 +52,11 @@ Future Things and Ideas
|
||||
|
||||
More optimization ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- if a for loop's loopvariable isn't referenced in the body, replace by a repeatloop
|
||||
- automatically convert if statements that test for multiple values (if X==1 or X==2..) to if X in [1,2,..] statements, instead of just a warning
|
||||
- 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'
|
||||
- translateFunctioncall() in BuiltinFunctionsAsmGen: should be able to assign parameters to a builtin function directly from register(s), this will make the use of a builtin function in a pipe expression more efficient without using a temporary variable
|
||||
- translateNormalAssignment() -> better code gen for assigning boolean comparison expressions
|
||||
- when a for loop's loopvariable isn't referenced in the body, and the iterations are known, replace the loop by a repeatloop
|
||||
- automatically convert if statements that test for multiple values (if X==1 or X==2..) to if X in [1,2,..] statements, instead of just a warning.
|
||||
- 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 and very limited now)
|
||||
and perhaps as well the assignment splitting in BeforeAsmAstChanger too
|
||||
- introduce byte-index operator to avoid index multiplications in loops over arrays? see github issue #4
|
||||
File renamed without changes.
+37
-24
@@ -1,33 +1,46 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
str s1 = "Irmen_"
|
||||
str s2 = @"IRMEN_"
|
||||
str s3 = sc:"IRMEN_"
|
||||
str s4 = iso:"Käse, Straße"
|
||||
|
||||
sub start() {
|
||||
%asm {{
|
||||
lda #<float5_111
|
||||
ldy #>float5_111
|
||||
jsr floats.MOVFM
|
||||
lda #<float5_122
|
||||
ldy #>float5_122
|
||||
jsr floats.FADD
|
||||
jsr floats.FOUT
|
||||
sta $7e
|
||||
sty $7f
|
||||
ldy #0
|
||||
_loop
|
||||
lda ($7e),y
|
||||
beq _done
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne _loop
|
||||
_done
|
||||
rts
|
||||
txt.iso()
|
||||
; txt.lowercase()
|
||||
txt.nl()
|
||||
txt.nl()
|
||||
txt.nl()
|
||||
txt.nl()
|
||||
txt.nl()
|
||||
txt.print(s1)
|
||||
txt.nl()
|
||||
txt.print(s2)
|
||||
txt.nl()
|
||||
txt.print(s3)
|
||||
txt.nl()
|
||||
txt.print(s4)
|
||||
txt.nl()
|
||||
|
||||
float5_111 .byte $81, $0e, $14, $7a, $e1 ; float 1.11
|
||||
float5_122 .byte $81, $1c, $28, $f5, $c2 ; float 1.22
|
||||
|
||||
}}
|
||||
sc(1, s1)
|
||||
sc(2, s2)
|
||||
sc(3, s3)
|
||||
sc(4, s4)
|
||||
}
|
||||
|
||||
sub sc(ubyte row, str text) {
|
||||
uword addr = 1024+row*40
|
||||
ubyte ix = 0
|
||||
ubyte ss
|
||||
repeat {
|
||||
ss = text[ix]
|
||||
if not ss
|
||||
return
|
||||
@(addr) = ss
|
||||
addr++
|
||||
ix++
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-14
@@ -28,7 +28,6 @@ DEC_INTEGER : ('0'..'9') | (('1'..'9')('0'..'9')+);
|
||||
HEX_INTEGER : '$' (('a'..'f') | ('A'..'F') | ('0'..'9'))+ ;
|
||||
BIN_INTEGER : '%' ('0' | '1')+ ;
|
||||
ADDRESS_OF: '&';
|
||||
ALT_STRING_ENCODING: '@';
|
||||
|
||||
FLOAT_NUMBER : FNUMBER (('E'|'e') ('+' | '-')? FNUMBER)? ; // sign comes later from unary expression
|
||||
fragment FNUMBER : ('0' .. '9') + ('.' ('0' .. '9') +)? ;
|
||||
@@ -45,18 +44,20 @@ SINGLECHAR :
|
||||
'\'' ( STRING_ESCAPE_SEQ | ~[\\\r\n\f'] ) '\''
|
||||
;
|
||||
|
||||
ZEROPAGE :
|
||||
'@zp'
|
||||
;
|
||||
ZEROPAGE : '@zp' ;
|
||||
|
||||
SHARED :
|
||||
'@shared'
|
||||
;
|
||||
ZEROPAGEREQUIRE : '@requirezp' ;
|
||||
|
||||
SHARED : '@shared' ;
|
||||
|
||||
ARRAYSIG :
|
||||
'[]'
|
||||
;
|
||||
|
||||
PIPE :
|
||||
'|>'
|
||||
;
|
||||
|
||||
cpuregister: 'A' | 'X' | 'Y';
|
||||
register: 'A' | 'X' | 'Y' | 'AX' | 'AY' | 'XY' | 'Pc' | 'Pz' | 'Pn' | 'Pv' | 'R0' | 'R1' | 'R2' | 'R3' | 'R4' | 'R5' | 'R6' | 'R7' | 'R8' | 'R9' | 'R10' | 'R11' | 'R12' | 'R13' | 'R14' | 'R15';
|
||||
|
||||
@@ -98,6 +99,7 @@ statement :
|
||||
| whenstmt
|
||||
| breakstmt
|
||||
| labeldef
|
||||
| pipestmt
|
||||
;
|
||||
|
||||
|
||||
@@ -128,7 +130,9 @@ directive :
|
||||
|
||||
directivearg : stringliteral | identifier | integerliteral ;
|
||||
|
||||
vardecl: datatype (arrayindex | ARRAYSIG)? SHARED? ZEROPAGE? varname=identifier ;
|
||||
vardecl: datatype (arrayindex | ARRAYSIG)? decloptions varname=identifier ;
|
||||
|
||||
decloptions: (SHARED | ZEROPAGE | ZEROPAGEREQUIRE)* ;
|
||||
|
||||
varinitializer : vardecl '=' expression ;
|
||||
|
||||
@@ -155,7 +159,8 @@ assign_target:
|
||||
postincrdecr : assign_target operator = ('++' | '--') ;
|
||||
|
||||
expression :
|
||||
functioncall
|
||||
'(' expression ')'
|
||||
| functioncall
|
||||
| <assoc=right> prefix = ('+'|'-'|'~') expression
|
||||
| left = expression EOL? bop = '**' EOL? right = expression
|
||||
| left = expression EOL? bop = ('*' | '/' | '%' ) EOL? right = expression
|
||||
@@ -178,13 +183,12 @@ expression :
|
||||
| directmemory
|
||||
| addressof
|
||||
| expression typecast
|
||||
| '(' expression ')'
|
||||
| pipesource = expression EOL? pipe=PIPE EOL? pipetarget = expression
|
||||
;
|
||||
|
||||
|
||||
typecast : 'as' datatype;
|
||||
|
||||
|
||||
arrayindexed : scoped_identifier arrayindex ;
|
||||
|
||||
directmemory : '@' '(' expression ')';
|
||||
@@ -204,6 +208,8 @@ returnstmt : 'return' expression? ;
|
||||
|
||||
breakstmt : 'break';
|
||||
|
||||
pipestmt: source=expression pipe=PIPE EOL? target=expression ;
|
||||
|
||||
identifier : NAME ;
|
||||
|
||||
scoped_identifier : NAME ('.' NAME)* ;
|
||||
@@ -214,9 +220,9 @@ booleanliteral : 'true' | 'false' ;
|
||||
|
||||
arrayliteral : '[' EOL? expression (',' EOL? expression)* EOL? ']' ; // you can split the values over several lines
|
||||
|
||||
stringliteral : ALT_STRING_ENCODING? STRING ;
|
||||
stringliteral : (old_alt_encoding='@' | encoding=NAME ':')? STRING ;
|
||||
|
||||
charliteral : ALT_STRING_ENCODING? SINGLECHAR ;
|
||||
charliteral : (old_alt_encoding='@' | encoding=NAME ':')? SINGLECHAR ;
|
||||
|
||||
floatliteral : FLOAT_NUMBER ;
|
||||
|
||||
@@ -295,4 +301,3 @@ repeatloop: 'repeat' expression? EOL? (statement | statement_block) ;
|
||||
whenstmt: 'when' expression '{' EOL (when_choice | EOL) * '}' EOL? ;
|
||||
|
||||
when_choice: (expression_list | 'else' ) '->' (statement | statement_block ) ;
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
</options>
|
||||
<keywords keywords="&;->;@;\$;and;as;asmsub;break;clobbers;do;downto;else;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;or;repeat;return;romsub;step;sub;to;true;until;when;while;xor;~" ignore_case="false" />
|
||||
<keywords2 keywords="%address;%asm;%asmbinary;%asminclude;%breakpoint;%import;%launcher;%option;%output;%zeropage;%zpreserved" />
|
||||
<keywords3 keywords="byte;const;float;shared;str;ubyte;uword;void;word;zp" />
|
||||
<keywords4 keywords="abs;acos;all;any;asin;atan;avg;callfar;callrom;ceil;cmp;cos;cos16;cos16u;cos8;cos8u;cosr16;cosr16u;cosr8;cosr8u;deg;floor;len;ln;log2;lsb;lsl;lsr;max;memory;min;mkword;msb;peek;peekw;poke;pokew;pop;popw;push;pushw;rad;reverse;rnd;rndf;rndw;rol;rol2;ror;ror2;round;rrestore;rrestorex;rsave;rsavex;sgn;sin;sin16;sin16u;sin8;sin8u;sinr16;sinr16u;sinr8;sinr8u;sizeof;sort;sqrt;sqrt16;sum;swap;tan" />
|
||||
<keywords3 keywords="@requirezp;@shared;@zp;byte;const;float;str;ubyte;uword;void;word" />
|
||||
<keywords4 keywords="abs;acos;all;any;asin;atan;avg;callfar;callrom;ceil;cmp;cos;cos16;cos16u;cos8;cos8u;cosr16;cosr16u;cosr8;cosr8u;deg;floor;len;ln;log2;lsb;lsl;lsr;max;memory;min;mkword;msb;peek;peekw;poke;pokew;pop;popw;push;pushw;rad;reverse;rnd;rndf;rndw;rol;rol2;ror;ror2;round;rrestore;rrestorex;rsave;rsavex;sgn;sin;sin16;sin16u;sin8;sin8u;sinr16;sinr16u;sinr8;sinr8u;sizeof;sort;sqrt;sqrt16;sum;swap;tan;|>" />
|
||||
</highlighting>
|
||||
<extensionMap>
|
||||
<mapping ext="p8" />
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
<Keywords name="Folders in comment, open"></Keywords>
|
||||
<Keywords name="Folders in comment, middle"></Keywords>
|
||||
<Keywords name="Folders in comment, close"></Keywords>
|
||||
<Keywords name="Keywords1">void const
str
byte ubyte
word uword
float
zp shared</Keywords>
|
||||
<Keywords name="Keywords1">void const
str
byte ubyte
word uword
float
zp shared requirezp</Keywords>
|
||||
<Keywords name="Keywords2">%address
%asm
%asmbinary
%asminclude
%breakpoint
%import
%launcher
%option
%output
%zeropage
%zpreserved</Keywords>
|
||||
<Keywords name="Keywords3">inline sub asmsub romsub
clobbers
asm
if
when else
if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z
for in step do while repeat
break return goto</Keywords>
|
||||
<Keywords name="Keywords4">abs acos all any asin atan avg callfar callrom ceil cmp cos cos16 cos16u cos8 cos8u cosr8 cosr8u cosr16 cosr16u deg floor len ln log2 lsb lsl lsr max memory min mkword msb peek peekw poke pokew push pushw pop popw rsave rsavex rrestore rrestorex rad reverse rnd rndf rndw rol rol2 ror ror2 round sgn sin sin16 sin16u sin8 sin8u sinr8 sinr8u sinr16 sinr16u sizeof sort sqrt sqrt16 sum swap tan
</Keywords>
|
||||
<Keywords name="Keywords5">true false
not and or xor
as to downto</Keywords>
|
||||
<Keywords name="Keywords4">abs acos all any asin atan avg callfar callrom ceil cmp cos cos16 cos16u cos8 cos8u cosr8 cosr8u cosr16 cosr16u deg floor len ln log2 lsb lsl lsr max memory min mkword msb peek peekw poke pokew push pushw pop popw rsave rsavex rrestore rrestorex rad reverse rnd rndf rndw rol rol2 ror ror2 round sgn sin sin16 sin16u sin8 sin8u sinr8 sinr8u sinr16 sinr16u sizeof sort sqrt sqrt16 sum swap tan</Keywords>
|
||||
<Keywords name="Keywords5">true false
not and or xor
as to downto |></Keywords>
|
||||
<Keywords name="Keywords6"></Keywords>
|
||||
<Keywords name="Keywords7"></Keywords>
|
||||
<Keywords name="Keywords8"></Keywords>
|
||||
|
||||
@@ -24,6 +24,7 @@ syn match prog8Function "\(\<\(asm\)\?sub\>\s\+\)\@16<=\<\w\+\>"
|
||||
syn match prog8Function "\(romsub\s\+$\x\+\s\+=\s\+\)\@16<=\<\w\+\>"
|
||||
|
||||
syn keyword prog8Statement break goto return asmsub sub inline
|
||||
syn match prog8Statement "|>"
|
||||
syn match prog8Statement "\<\(asm\|rom\)\?sub\>"
|
||||
syn keyword prog8Conditional if else when
|
||||
syn keyword prog8Conditional if_cs if_cc if_vs if_vc if_eq if_z if_ne if_nz
|
||||
@@ -43,7 +44,7 @@ syn region prog8ArrayType matchgroup=prog8Type
|
||||
\ start="\<\%(u\?byte\|u\?word\|float\|str\)\[" end="\]"
|
||||
\ transparent
|
||||
syn keyword prog8StorageClass const
|
||||
syn match prog8StorageClass "\(^\|\s\)\(@zp\|@shared\)\>"
|
||||
syn match prog8StorageClass "\(^\|\s\)\(@zp\|@shared\|@requirezp\)\>"
|
||||
|
||||
syn region prog8Block start="{" end="}" transparent
|
||||
syn region prog8Expression start="(" end=")" transparent
|
||||
|
||||
Reference in new issue
Block a user