Compare commits

...
53 Commits
Author SHA1 Message Date
Irmen de Jong 53bf8c09fd fix screencode encoding selection 2022-01-19 21:37:27 +01:00
Irmen de Jong 651c383668 refactor encoder to be the same for all 3 machine targets now 2022-01-19 21:21:33 +01:00
Irmen de Jong 9ed7587e3e document new string encoding syntax 2022-01-19 21:21:33 +01:00
Irmen de Jong 674295e800 improve error reporting from string encoders 2022-01-19 21:21:33 +01:00
Irmen de Jong 6b02f2eea0 implement iso encoding and new string encoding syntax, fixes #38 2022-01-19 21:21:32 +01:00
Irmen de Jong 5237e55326 added txt.iso() to enable iso-charset on cx16 2022-01-18 21:35:29 +01:00
Irmen de Jong 3b59592110 generalize string encoding flag into enum 2022-01-18 21:21:49 +01:00
Irmen de Jong 72640ae058 no longer add nops around breakpoint for vice 2022-01-17 22:12:58 +01:00
Irmen de Jong d916027e75 labels no longer start with '_' fixes #62 2022-01-17 22:03:53 +01:00
Irmen de Jong 8966d2aa06 comments and prepare new version 7.7 2022-01-16 23:03:00 +01:00
Irmen de Jong de7ea04f54 when zp option = dontuse, print error for any variable with @requirezp 2022-01-16 18:13:24 +01:00
Irmen de Jong bf71fabe0e fix codegen mistake for zp allocated loop vars 2022-01-16 18:09:09 +01:00
Irmen de Jong b3368acb33 todos to fix broken examples 2022-01-16 17:57:47 +01:00
Irmen de Jong 87220c6697 docs for @requirezp 2022-01-16 17:20:36 +01:00
Irmen de Jong a3b5c2ad71 fix zp address output and adjust vars datastructure 2022-01-16 17:20:36 +01:00
Irmen de Jong fb4c1473c5 array and string initialization in zeropage 2022-01-16 17:20:36 +01:00
Irmen de Jong 2bb2502d20 added @requirezp to syntax files 2022-01-16 17:20:36 +01:00
Irmen de Jong fe51698579 tweak how zp varnames are stored 2022-01-16 17:20:36 +01:00
Irmen de Jong 0f0f40bff3 improved ForloopAsmGen loopvar zp allocation 2022-01-16 17:20:36 +01:00
Irmen de Jong a798fe72d3 cx16 reserved zp vars (virtual registers) 2022-01-16 17:20:36 +01:00
Irmen de Jong fba98d03a5 improve %zpreserved error messages 2022-01-16 17:20:36 +01:00
Irmen de Jong 7dd2517f67 fix Zp allocation issues 2022-01-16 17:20:36 +01:00
Irmen de Jong 641477d6f6 add @requirezp and allow str/array to be on zp (with warning) 2022-01-16 17:20:32 +01:00
Irmen de Jong 8e56656c8d fix broken code generated for certain ==/!= expressions 2022-01-16 17:10:49 +01:00
Irmen de Jong 564a6a1f62 fix invalid removal of Jump
that would generate wrong code if occurs at the end of a subroutine
2022-01-16 14:05:42 +01:00
Irmen de Jong 69f0c80cd7 added pokemon() function 2022-01-15 19:04:04 +01:00
Irmen de Jong 6fcb51cea2 add warning when encoded string contains 0-byte 2022-01-15 17:11:40 +01:00
Irmen de Jong c58b8a4973 fix ast to source: @shared wasn't printed
fix grammar: @shared and @zp can occur in any order now in vardecl
2022-01-13 02:29:55 +01:00
Irmen de Jong c8f4ab4f06 doc 2022-01-12 22:21:01 +01:00
Irmen de Jong e425c4cca8 optimizing pipe codegen 2022-01-11 23:17:35 +01:00
Irmen de Jong 056ec986c2 use var initializer assignments in a clearer way 2022-01-11 00:34:44 +01:00
Irmen de Jong de3b2fb95b slightly optimized certain list iterations into sequences 2022-01-10 23:15:24 +01:00
Irmen de Jong 789e39c719 slightly optimized assembly file handling 2022-01-10 22:48:20 +01:00
Irmen de Jong b29c3152db Assignment: make its origin explicit 2022-01-10 02:25:02 +01:00
Irmen de Jong 3831679772 VarDecl: make its origin explicit 2022-01-10 01:53:03 +01:00
Irmen de Jong 596f9566d8 todo 2022-01-10 01:00:50 +01:00
Irmen de Jong 124befe9d6 slightly optimized code for assigning boolean expressions such as `b = xx>99` 2022-01-09 18:49:44 +01:00
Irmen de Jong 895534f32b don't remove dead variable assignments if they are a function call 2022-01-09 18:41:01 +01:00
Irmen de Jong 50c16fe6de code size optimization: don't copy floats with inlined copy code but use copy_float routine 2022-01-09 16:18:13 +01:00
Irmen de Jong b092d1a5d3 fixed code gen issue for uword >= comparison 2022-01-09 13:23:29 +01:00
Irmen de Jong a9b45630d7 optimized code for variable comparisons to zero 2022-01-09 13:10:01 +01:00
Irmen de Jong c1a39c269e optimized code for stack eval comparisons with zero 2022-01-09 03:19:49 +01:00
Irmen de Jong 6fa3f0b6cd small refactor 2022-01-08 18:02:38 +01:00
Irmen de Jong 9e5e3d1559 pipe expression not evaluated via stack 2022-01-08 17:51:23 +01:00
Irmen de Jong 7135205299 fix codegen bug for pipe expressions to actually return correct value and not corrupt X register 2022-01-08 17:41:46 +01:00
Irmen de Jong d99d977d2b fix more typecasting issues 2022-01-08 17:04:25 +01:00
Irmen de Jong 7dd7e562bc pipes also as expressions, cleanup codegen, fix various typecasting issues 2022-01-08 13:45:19 +01:00
Irmen de Jong 75d857027e cleanup of Pipe codegen 2022-01-08 01:33:40 +01:00
Irmen de Jong 17694c1d01 better error handling of invalid number casts 2022-01-07 22:12:13 +01:00
Irmen de Jong 749ad700d8 naming consistency for some expression classes 2022-01-07 21:02:55 +01:00
Irmen de Jong 8f3df3039a added pipe operator `|>` 2022-01-06 22:54:18 +01:00
Irmen de Jong 02c315c194 add missing unit tests and type checking for 'in' expression 2022-01-06 00:01:49 +01:00
Irmen de Jong b697375573 add note about unspecified order of evaluation of expressions and subroutine call arguments 2022-01-05 23:21:45 +01:00
95 changed files with 3190 additions and 1438 deletions

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)
}
}
}
@@ -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
@@ -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("""
@@ -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)
}
}
}
}
+69 -8
View File
@@ -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
+6 -6
View File
@@ -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
}}
}
}
+15 -2
View File
@@ -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
+5 -5
View File
@@ -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
+101 -1
View File
@@ -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
View File
@@ -1 +1 @@
7.6
7.7
+4 -3
View File
@@ -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
}
@@ -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)
+6 -6
View File
@@ -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
}
+1 -1
View File
@@ -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)
+18 -17
View File
@@ -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
View File
@@ -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)
+9 -8
View File
@@ -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)
+22 -38
View File
@@ -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
View File
@@ -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'
}
})
+146
View File
@@ -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"
}
})
-1
View File
@@ -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
+188
View File
@@ -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
}
})
+68 -89
View File
@@ -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
}
})
+52 -8
View File
@@ -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")
}
}
+229 -20
View File
@@ -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" {
+34 -6
View File
@@ -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--
}
}
+2 -2
View File
@@ -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 -8
View File
@@ -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
+63 -63
View File
@@ -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())
}
+1
View File
@@ -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
}
}
+22 -6
View File
@@ -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)
}
}
+10 -2
View File
@@ -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
}
+1
View File
@@ -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
View File
@@ -5,7 +5,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="Python 3.9 (py3)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.10 (py3)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+1
View File
@@ -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).
+3 -3
View File
@@ -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
View File
@@ -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.
+42 -8
View File
@@ -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``.
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 ) ;
+2 -2
View File
@@ -13,8 +13,8 @@
</options>
<keywords keywords="&amp;;-&gt;;@;\$;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;|&gt;" />
</highlighting>
<extensionMap>
<mapping ext="p8" />
+3 -3
View File
@@ -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&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte&#x000D;&#x000A;word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared</Keywords>
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte&#x000D;&#x000A;word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared requirezp</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%import&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved</Keywords>
<Keywords name="Keywords3">inline sub asmsub romsub&#x000D;&#x000A;clobbers&#x000D;&#x000A;asm&#x000D;&#x000A;if&#x000D;&#x000A;when else&#x000D;&#x000A;if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z&#x000D;&#x000A;for in step do while repeat&#x000D;&#x000A;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&#x000D;&#x000A;</Keywords>
<Keywords name="Keywords5">true false&#x000D;&#x000A;not and or xor&#x000D;&#x000A;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&#x000D;&#x000A;not and or xor&#x000D;&#x000A;as to downto |&gt;</Keywords>
<Keywords name="Keywords6"></Keywords>
<Keywords name="Keywords7"></Keywords>
<Keywords name="Keywords8"></Keywords>
+2 -1
View File
@@ -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