Merge branch 'master' into structs

# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
#	compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
#	compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt
#	compilerAst/src/prog8/ast/AstToSourceTextConverter.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	compilerAst/src/prog8/ast/walk/AstWalker.kt
#	compilerAst/src/prog8/ast/walk/IAstVisitor.kt
#	docs/source/todo.rst
#	examples/test.p8
#	parser/src/main/antlr/Prog8ANTLR.g4
This commit is contained in:
Irmen de Jong
2025-05-11 23:16:03 +02:00
37 changed files with 431 additions and 93 deletions

View File

@@ -71,6 +71,7 @@ What does Prog8 provide?
- high-level program optimizations
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``on .. goto`` statement for fast jump tables
- ``in`` expression for concise and efficient multi-value/containment check
- ``defer`` statement to help write concise and robust subroutine cleanup logic
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``

View File

@@ -6,7 +6,10 @@ import prog8.code.target.zp.C128Zeropage
import java.nio.file.Path
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
class C128Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
@@ -28,10 +31,10 @@ class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by N
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override val BSSGOLDENRAM_START = 0u // TODO
override val BSSGOLDENRAM_END = 0u // TODO
override val BSSHIGHRAM_START = 0u // TODO address?
override val BSSHIGHRAM_END = 0u // TODO address?
override val BSSGOLDENRAM_START = 0u // TODO address?
override val BSSGOLDENRAM_END = 0u // TODO address?
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam

View File

@@ -7,7 +7,10 @@ import java.io.IOException
import java.nio.file.Path
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class C64Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null

View File

@@ -37,7 +37,7 @@ class ConfigFileTarget(
val zpFullsafe: List<UIntRange>,
val zpKernalsafe: List<UIntRange>,
val zpBasicsafe: List<UIntRange>
): ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(8) {
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) {
companion object {

View File

@@ -6,7 +6,10 @@ import prog8.code.target.zp.CX16Zeropage
import java.nio.file.Path
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class Cx16Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null

View File

@@ -6,7 +6,10 @@ import prog8.code.target.zp.PETZeropage
import java.nio.file.Path
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class PETTarget: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null

View File

@@ -7,7 +7,10 @@ import kotlin.io.path.isReadable
import kotlin.io.path.name
import kotlin.io.path.readText
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
class VMTarget: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.ISO
override val libraryPath = null

View File

@@ -197,6 +197,7 @@ object AtasciiEncoding {
fun encode(str: String): Result<List<UByte>, CharConversionException> {
val mapped = str.map { chr ->
when (chr) {
'\r' -> 0x9bu
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly

View File

@@ -285,6 +285,7 @@ object C64osEncoding {
val screencode = encodingC64os[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 13u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()

View File

@@ -5,19 +5,19 @@ import prog8.code.core.Encoding
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
object Encoder: IStringEncoding {
class Encoder(val newlineToCarriageReturn: Boolean): IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
Encoding.ISO -> IsoEncoding.encode(str)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str)
Encoding.ISO16 -> IsoEasternEncoding.encode(str)
Encoding.ISO -> IsoEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.encode(str, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.C64OS -> C64osEncoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
@@ -30,12 +30,12 @@ object Encoder: IStringEncoding {
val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
Encoding.ISO -> IsoEncoding.decode(bytes)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes)
Encoding.ISO -> IsoEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.C64OS -> C64osEncoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}

View File

@@ -6,14 +6,16 @@ import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
open class IsoEncodingBase(charsetName: String) {
val charset: Charset = Charset.forName(charsetName)
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
'\n' -> if(newlineToCarriageReturn) 13u else 10u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
@@ -27,9 +29,14 @@ open class IsoEncodingBase(charsetName: String) {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}

View File

@@ -64,10 +64,11 @@ object JapaneseCharacterConverter {
object KatakanaEncoding {
val charset: Charset = Charset.forName("JIS_X0201")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\n' -> if(newlineToCarriageReturn) 13u else 10u
'\u0000' -> 0u
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves
@@ -112,9 +113,14 @@ object KatakanaEncoding {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}

View File

@@ -21,7 +21,7 @@ object PetsciiEncoding {
'\ufffe', // 0x07 -> UNDEFINED
'\uf118', // 0x08 -> DISABLE CHARACTER SET SWITCHING (CUS)
'\uf119', // 0x09 -> ENABLE CHARACTER SET SWITCHING (CUS)
'\ufffe', // 0x0A -> UNDEFINED
'\n', // 0x0A -> LINE FEED (RETURN)
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\n' , // 0x0D -> LINE FEED (RETURN)
@@ -1117,6 +1117,8 @@ object PetsciiEncoding {
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 141u
'\r' -> 141u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()

View File

@@ -1034,6 +1034,7 @@ $repeatLabel""")
val target = getJumpTarget(jump)
require(!target.needsExpressionEvaluation)
if(target.indirect) {
require(!target.indexedX)
val complementedInstruction = branchInstruction(stmt.condition, true)
out("""
$complementedInstruction +
@@ -1085,18 +1086,24 @@ $repeatLabel""")
else {
if(evaluateAddressExpression) {
val arrayIdx = jump.target as? PtArrayIndexer
if (isTargetCpu(CpuType.CPU65C02) && arrayIdx!=null) {
if (!arrayIdx.splitWords) {
// if the jump target is an address in a non-split array (like a jump table of only pointers),
// on the 65c02, more optimal assembly can be generated using JMP address,X
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
out(" asl a | tax")
return JumpTarget(asmSymbolName(arrayIdx.variable), true, true, false)
if (arrayIdx!=null) {
if (isTargetCpu(CpuType.CPU65C02)) {
if (!arrayIdx.splitWords) {
// if the jump target is an address in a non-split array (like a jump table of only pointers),
// on the 65c02, more optimal assembly can be generated using JMP (address,X)
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
out(" asl a | tax")
return JumpTarget(asmSymbolName(arrayIdx.variable), true, true, false)
} else {
// print a message when more optimal code is possible for 65C02 cpu
val variable = symbolTable.lookup(arrayIdx.variable.name)!!
if(variable is StStaticVariable && variable.length!!<=128u)
errors.info("the jump address array is @split, but @nosplit would create more efficient code here", jump.position)
}
} else {
// print a message when more optimal code is possible
val variable = symbolTable.lookup(arrayIdx.variable.name)!!
if(variable is StStaticVariable && variable.length!!<=128u)
errors.info("the jump address array is @split, but @nosplit would create more efficient code here", jump.position)
// print a message when more optimal code is possible for 6502 cpu
if(!arrayIdx.splitWords)
errors.info("the jump address array is @nosplit, but @split would create more efficient code here", jump.position)
}
}
// we can do the address evaluation right now and just use a temporary pointer variable

View File

@@ -475,7 +475,7 @@ private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? {
val identifier = identMatch.value
when (val symbol = symbolTable.flat[identifier]) {
is StConstant -> symbol.value.toUInt()
is StMemVar -> symbol.address.toUInt()
is StMemVar -> symbol.address
else -> null
}
} else null
@@ -512,6 +512,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
// rts + jmp -> remove jmp
// rts + bxx -> remove bxx
// lda + cmp #0 -> remove cmp, same for cpy and cpx.
// bra/jmp + bra/jmp -> remove second bra/jmp
// and some other optimizations.
val mods = mutableListOf<Modification>()
@@ -567,6 +568,12 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
}
}
}
if(" bra" in first || "\tbra" in first || " jmp" in first || "\tjmp" in first ) {
if(" bra" in second || "\tbra" in second || " jmp" in second || "\tjmp" in second ) {
mods.add(Modification(lines[1].index, true, null))
}
}
}
/*

View File

@@ -86,6 +86,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bmi", target)
}
else
@@ -93,6 +94,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bpl", target)
}
else
@@ -106,6 +108,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvs", target)
}
else
@@ -113,6 +116,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else {
if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvc", target)
}
else
@@ -171,9 +175,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" $falseBranch +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
asmgen.out("""
jmp (${target.asmLabel})
+""")
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
asmgen.out("+")
} else {
require(!target.needsExpressionEvaluation)
asmgen.out(" $branchInstr ${target.asmLabel}")
@@ -286,6 +289,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" bmi + | beq +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -353,6 +357,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" bmi + | bne ++")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -434,6 +439,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" bcc + | beq +")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -535,6 +541,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
+ bpl +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -590,6 +597,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
bcs +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
_jump jmp (${target.asmLabel})
+""")
@@ -667,6 +675,7 @@ _jump jmp (${target.asmLabel})
+ bpl +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -721,6 +730,7 @@ _jump jmp (${target.asmLabel})
bcc +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -829,6 +839,7 @@ _jump jmp (${target.asmLabel})
bne ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -908,6 +919,7 @@ _jump jmp (${target.asmLabel})
bne ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -973,6 +985,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1053,6 +1066,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1196,6 +1210,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1248,6 +1263,7 @@ _jump jmp (${target.asmLabel})
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -1302,6 +1318,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1361,6 +1378,7 @@ _jump jmp (${target.asmLabel})
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")
@@ -1423,6 +1441,7 @@ _jump jmp (${target.asmLabel})
beq ++""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
+ jmp (${target.asmLabel})
+""")
@@ -1481,6 +1500,7 @@ _jump jmp (${target.asmLabel})
bne +""")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out("""
jmp (${target.asmLabel})
+""")

View File

@@ -258,8 +258,8 @@ class IRCodeGen(
is PtBool,
is PtArray,
is PtBlock,
is PtDefer -> throw AssemblyError("should have been transformed")
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtString -> throw AssemblyError("string should not occur as separate statement node ${node.position}")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
is PtStructDecl -> emptyList()
is PtSubSignature -> emptyList()

View File

@@ -424,7 +424,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report()
program.reorderStatements(errors)
errors.report()
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
errors.report()
program.changeNotExpressionAndIfComparisonExpr(errors, compilerOptions.compTarget)
errors.report()
@@ -486,7 +486,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
}
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
program.addTypecasts(errors, compilerOptions)
errors.report()
program.variousCleanups(errors, compilerOptions)

View File

@@ -2356,6 +2356,12 @@ internal class AstChecker(private val program: Program,
return false
}
override fun visit(onGoto: OnGoto) {
if(!onGoto.index.inferType(program).getOrUndef().isUnsignedByte) {
errors.err("on..goto index must be an unsigned byte", onGoto.index.position)
}
}
}
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, errors: IErrorReporter) {

View File

@@ -103,8 +103,8 @@ internal fun Program.addTypecasts(errors: IErrorReporter, options: CompilationOp
caster.applyModifications()
}
fun Program.desugaring(errors: IErrorReporter) {
val desugar = CodeDesugarer(this, errors)
fun Program.desugaring(errors: IErrorReporter, options: CompilationOptions) {
val desugar = CodeDesugarer(this, options.compTarget, errors)
desugar.visit(this)
while(errors.noErrors() && desugar.applyModifications()>0)
desugar.visit(this)

View File

@@ -8,7 +8,7 @@ import prog8.ast.walk.IAstModification
import prog8.code.core.*
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
internal class CodeDesugarer(val program: Program, private val target: ICompilationTarget, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer !
@@ -24,6 +24,7 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
// - flatten chained assignments
// - remove alias nodes
// - convert on..goto/call to jumpaddr array and separate goto/call
// - replace implicit pointer dereference chains (a.b.c.d) with explicit ones (a^^.b^^.c^^.d)
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
@@ -394,4 +395,60 @@ _after:
}
return noModifications
}
override fun after(ongoto: OnGoto, parent: Node): Iterable<IAstModification> {
val indexDt = ongoto.index.inferType(program).getOrUndef()
if(!indexDt.isUnsignedByte)
return noModifications
val numlabels = ongoto.labels.size
val split = if(ongoto.isCall)
true // for calls (indirect JSR), split array is always the optimal choice
else
target.cpu==CpuType.CPU6502 // for goto (indirect JMP), split array is optimal for 6502, but NOT for the 65C02 (it has a different JMP addressing mode available)
val arrayDt = DataType.arrayFor(BaseDataType.UWORD, split)
val labelArray = ArrayLiteral(InferredTypes.knownFor(arrayDt), ongoto.labels.toTypedArray(), ongoto.position)
val jumplistArray = VarDecl.createAutoOptionalSplit(labelArray)
val indexValue: Expression
val conditionVar: VarDecl?
val assignIndex: Assignment?
// put condition in temp var, if it is not simple; to avoid evaluating expression multiple times
if (ongoto.index.isSimple) {
indexValue = ongoto.index
assignIndex = null
conditionVar = null
} else {
conditionVar = VarDecl.createAuto(indexDt)
indexValue = IdentifierReference(listOf(conditionVar.name), conditionVar.position)
val varTarget = AssignTarget(indexValue, null, null, null, false, position=conditionVar.position)
assignIndex = Assignment(varTarget, ongoto.index, AssignmentOrigin.USERCODE, ongoto.position)
}
val callTarget = ArrayIndexedExpression(IdentifierReference(listOf(jumplistArray.name), jumplistArray.position), ArrayIndex(indexValue.copy(), indexValue.position), ongoto.position)
val callIndexed = AnonymousScope(mutableListOf(), ongoto.position)
if(ongoto.isCall) {
callIndexed.statements.add(FunctionCallStatement(IdentifierReference(listOf("call"), ongoto.position), mutableListOf(callTarget), true, ongoto.position))
} else {
callIndexed.statements.add(Jump(callTarget, ongoto.position))
}
val ifSt = if(ongoto.elsepart==null || ongoto.elsepart!!.isEmpty()) {
// if index<numlabels call(labels[index])
val compare = BinaryExpression(indexValue.copy(), "<", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
IfElse(compare, callIndexed, AnonymousScope(mutableListOf(), ongoto.position), ongoto.position)
} else {
// if index>=numlabels elselabel() else call(labels[index])
val compare = BinaryExpression(indexValue.copy(), ">=", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
IfElse(compare, ongoto.elsepart!!, callIndexed, ongoto.position)
}
val replacementScope = AnonymousScope(if(conditionVar==null)
mutableListOf(ifSt, jumplistArray)
else
mutableListOf(conditionVar, assignIndex!!, ifSt, jumplistArray)
, ongoto.position)
return listOf(IAstModification.ReplaceNode(ongoto, replacementScope, parent))
}
}

View File

@@ -77,6 +77,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
is StructDecl -> transform(statement)
is When -> transform(statement)
is WhileLoop -> throw FatalAstException("while loops must have been converted to jumps")
is OnGoto -> throw FatalAstException("ongoto must have been converted to array and separate call/goto")
is StructFieldRef -> throw FatalAstException("should not occur as part of the actual AST")
}
}
@@ -950,7 +951,6 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
return cast
}
private fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (SourceCode.isLibraryResource(filename)) {
return com.github.michaelbull.result.runCatching {

View File

@@ -19,11 +19,7 @@ import prog8.code.core.Position
import prog8.code.core.unescape
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.encodings.Encoder
import prog8.code.target.encodings.AtasciiEncoding
import prog8.code.target.encodings.C64osEncoding
import prog8.code.target.encodings.IsoEncoding
import prog8.code.target.encodings.PetsciiEncoding
import prog8.code.target.encodings.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import java.io.CharConversionException
@@ -227,7 +223,7 @@ class TestStringEncodings: FunSpec({
context("iso") {
test("iso accepts iso-characters") {
val result = IsoEncoding.encode("a_~ëç")
val result = IsoEncoding.encode("a_~ëç", false)
result.getOrElse { throw it }.map {it.toInt()} shouldBe listOf(97, 95, 126, 235, 231)
}
@@ -276,13 +272,13 @@ class TestStringEncodings: FunSpec({
passthrough[1] shouldBe '\u801b'
passthrough[2] shouldBe '\u8099'
passthrough[3] shouldBe '\u80ff'
var encoded = Encoder.encodeString(passthrough, Encoding.PETSCII)
var encoded = Encoder(false).encodeString(passthrough, Encoding.PETSCII)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
encoded = Encoder.encodeString(passthrough, Encoding.ATASCII)
encoded = Encoder(false).encodeString(passthrough, Encoding.ATASCII)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
encoded = Encoder.encodeString(passthrough, Encoding.SCREENCODES)
encoded = Encoder(false).encodeString(passthrough, Encoding.SCREENCODES)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
encoded = Encoder.encodeString(passthrough, Encoding.ISO)
encoded = Encoder(false).encodeString(passthrough, Encoding.ISO)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
}
@@ -413,5 +409,39 @@ class TestStringEncodings: FunSpec({
val char2 = (main.statements[5] as Assignment).value as NumericLiteral
char2.number shouldBe 80.0
}
test("with newline conversion") {
val encoder = Encoder(true)
encoder.encodeString("\n\r", Encoding.PETSCII) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.SCREENCODES) shouldBe listOf<UByte>(141u, 141u)
encoder.encodeString("\n\r", Encoding.ATASCII) shouldBe listOf<UByte>(155u, 155u)
encoder.encodeString("\n\r", Encoding.ISO) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.ISO5) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.ISO16) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.CP437) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.KATAKANA) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.C64OS) shouldBe listOf<UByte>(13u, 13u)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.PETSCII).map { it.code } shouldBe listOf(10, 10)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.ISO).map { it.code } shouldBe listOf(10, 10)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.CP437).map { it.code } shouldBe listOf(10, 13)
}
test("without newline conversion") {
val encoder = Encoder(false)
encoder.encodeString("\n\r", Encoding.PETSCII) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.SCREENCODES) shouldBe listOf<UByte>(141u, 141u)
encoder.encodeString("\n\r", Encoding.ATASCII) shouldBe listOf<UByte>(155u, 155u)
encoder.encodeString("\n\r", Encoding.ISO) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.ISO5) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.ISO16) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.CP437) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.KATAKANA) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.C64OS) shouldBe listOf<UByte>(13u, 13u)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.PETSCII).map { it.code } shouldBe listOf(10, 10)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.ISO).map { it.code } shouldBe listOf(10, 13)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.CP437).map { it.code } shouldBe listOf(10, 13)
}
})

View File

@@ -1079,6 +1079,44 @@ main {
compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
}
test("on..goto") {
val src="""
main {
sub start() {
cx16.r13L = 1
cx16.r12L = 0
on cx16.r12L+1 call (
thing.func1,
thing.func2,
thing.func3)
else {
; not jumped
cx16.r0++
}
on cx16.r13L+1 goto (thing.func1, thing.func2, thing.func3)
}
}
thing {
sub func1() {
cx16.r10 += 1
}
sub func2() {
cx16.r10 += 2
}
sub func3() {
cx16.r10 += 3
}
}"""
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
}
}
})

View File

@@ -532,6 +532,22 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
output("alias ${alias.alias} = ${alias.target.nameInSource.joinToString(".")}")
}
override fun visit(onGoto: OnGoto) {
output(if(onGoto.isCall) "selectcall " else "selectgoto ")
onGoto.index.accept(this)
output(" from (")
onGoto.labels.forEachIndexed { idx, label ->
label.accept(this)
if(idx!=onGoto.labels.lastIndex)
output(", ")
}
outputln(")")
if(onGoto.elsepart!=null && onGoto.elsepart.isNotEmpty()) {
output(" else ")
onGoto.elsepart.accept(this)
}
}
override fun visit(deref: PtrDereference) {
output("${deref.identifier.nameInSource.joinToString(".")}^^")
deref.chain.forEach {

View File

@@ -169,6 +169,9 @@ private fun StatementContext.toAst() : Statement {
val aliasstmt = alias()?.toAst()
if(aliasstmt!=null) return aliasstmt
val ongotostmt = ongoto()?.toAst()
if(ongotostmt!=null) return ongotostmt
val structdecl = structdeclaration()?.toAst()
if(structdecl!=null) return structdecl
@@ -176,7 +179,7 @@ private fun StatementContext.toAst() : Statement {
}
private fun AsmsubroutineContext.toAst(): Subroutine {
val inline = this.inline()!=null
val inline = this.INLINE()!=null
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
@@ -250,13 +253,17 @@ private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter> = asmsub_
val identifiers = vardecl.identifierlist()?.identifier() ?: emptyList()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
val identifiername = identifiers.single().name()
val identifiername = getname(identifiers.single())
AsmSubroutineParameter(identifiername, datatype, registerorpair, statusregister, toPosition())
}
private fun IdentifierContext.name(): String {
return (this.UNICODEDNAME().text ?: this.UNDERSCORENAME()?.text)!!
}
private fun getname(identifier: IdentifierContext): String = identifier.children[0].text
// return if (identifier == null) null
// else identifier.children[0].text
// //else (identifier.NAME() ?: identifier.UNDERSCORENAME() ?: identifier.ON() ?: identifier.CALL()).text
//}
private fun parseParamRegister(registerTok: Token?, pos: Position): Pair<RegisterOrPair?, Statusflag?> {
if(registerTok==null)
@@ -354,7 +361,7 @@ private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
val identifiers = decl.identifierlist()?.identifier() ?: emptyList()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
val identifiername = identifiers.single().name()
val identifiername = getname(identifiers.single())
val (registerorpair, statusregister) = parseParamRegister(it.register, it.toPosition())
if(statusregister!=null) {
@@ -401,7 +408,7 @@ private fun Assign_targetContext.toAst() : AssignTarget {
AssignTarget(null, arrayindexed, null, null, false, position = toPosition())
}
is VoidTargetContext -> {
AssignTarget(null, null, null, null, true, position = void_().toPosition())
AssignTarget(null, null, null, null, true, position = voidtarget().toPosition())
}
is PointerDereferenceTargetContext -> {
val deref = this.pointerdereference().toAst()
@@ -844,7 +851,7 @@ private fun When_choiceContext.toAst(): WhenChoice {
private fun StructfielddeclContext.toAst(): Pair<DataType, List<String>> {
val identifiers = identifierlist()?.identifier() ?: emptyList()
val dt = datatype().toAst()
return dt to identifiers.map { it.name() }
return dt to identifiers.map { getname(it) }
}
private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
@@ -856,7 +863,7 @@ private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl
}
val zp = getZpOption(tags)
val split = getSplitOption(tags)
val identifiers = identifierlist().identifier().map { it.name() }
val identifiers = identifierlist().identifier().map { getname(it) }
val name = if(identifiers.size==1) identifiers[0] else "<multiple>"
val isArray = ARRAYSIG() != null || arrayindex() != null
val alignword = "@alignword" in tags
@@ -894,3 +901,12 @@ private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl
toPosition()
)
}
private fun OngotoContext.toAst(): Statement {
val elseStatements = this.else_part()?.toAst()
val elseScope = if(elseStatements==null) null else AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
val isCall = this.kind.text == "call"
val index = this.expression().toAst()
val labels = directivenamelist().scoped_identifier().map { it.toAst() }
return OnGoto(isCall, index, labels, elseScope, toPosition())
}

View File

@@ -284,7 +284,7 @@ class VarDecl(val type: VarDeclType,
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
var arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") }
if(arrayDt.isSplitWordArray) {
// autovars for array literals are NOT stored as a split word array!
// autovars for array literals are NEVER stored as a split word array!
when(arrayDt.sub) {
BaseDataType.WORD -> arrayDt = DataType.arrayFor(BaseDataType.WORD, false)
BaseDataType.UWORD -> arrayDt = DataType.arrayFor(BaseDataType.UWORD, false)
@@ -296,6 +296,24 @@ class VarDecl(val type: VarDeclType,
SplitWish.NOSPLIT, arraysize, autoVarName, emptyList(), array,
sharedWithAsm = false, alignment = 0u, dirty = false, position = array.position)
}
fun createAutoOptionalSplit(array: ArrayLiteral): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") }
val split = if(arrayDt.isSplitWordArray) SplitWish.SPLIT else if(arrayDt.isWordArray) SplitWish.NOSPLIT else SplitWish.DONTCARE
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, arrayDt, ZeropageWish.NOT_IN_ZEROPAGE,
split, arraysize, autoVarName, emptyList(), array,
sharedWithAsm = false, alignment = 0u, dirty = false, position = array.position)
}
fun createAuto(dt: DataType): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val vardecl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, dt, ZeropageWish.NOT_IN_ZEROPAGE,
SplitWish.DONTCARE, null, autoVarName, emptyList(), null,
sharedWithAsm = false, alignment = 0u, dirty = false, position = Position.DUMMY)
return vardecl
}
}
val isArray: Boolean
@@ -1304,3 +1322,29 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
override fun copy() = DirectMemoryWrite(addressExpression.copy(), position)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = addressExpression.referencesIdentifier(nameInSource)
}
class OnGoto(
val isCall: Boolean,
val index: Expression,
val labels: List<IdentifierReference>,
val elsepart: AnonymousScope?,
override val position: Position
) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
index.linkParents(this)
labels.forEach { it.linkParents(this) }
elsepart?.linkParents(this)
}
override fun copy(): OnGoto = OnGoto(isCall, index.copy(), labels.map { it.copy() }, elsepart?.copy(), position)
override fun referencesIdentifier(nameInSource: List<String>) = index.referencesIdentifier(nameInSource) || labels.any { it.referencesIdentifier(nameInSource) } || elsepart?.referencesIdentifier(nameInSource) == true
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) {
throw FatalAstException("can't replace")
}
}

View File

@@ -141,6 +141,7 @@ 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(ongoto: OnGoto, parent: Node): Iterable<IAstModification> = noModifications
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
open fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> = noModifications
@@ -190,6 +191,7 @@ 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(ongoto: OnGoto, parent: Node): Iterable<IAstModification> = noModifications
protected val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
@@ -523,6 +525,14 @@ abstract class AstWalker {
track(after(chainedAssignment, parent), chainedAssignment, parent)
}
fun visit(ongoto: OnGoto, parent: Node) {
track(before(ongoto, parent), ongoto, parent)
ongoto.index.accept(this, ongoto)
ongoto.labels.forEach { it.accept(this, ongoto) }
ongoto.elsepart?.accept(this, ongoto)
track(after(ongoto, parent), ongoto, parent)
}
fun visit(deref: PtrDereference, parent: Node) {
track(before(deref, parent), deref, parent)
deref.identifier.accept(this, deref)

View File

@@ -208,6 +208,12 @@ interface IAstVisitor {
chainedAssignment.nested.accept(this)
}
fun visit(onGoto: OnGoto) {
onGoto.index.accept(this)
onGoto.labels.forEach { it.accept(this) }
onGoto.elsepart?.accept(this)
}
fun visit(deref: PtrDereference) {
deref.identifier.accept(this)
}

View File

@@ -101,6 +101,7 @@ Features
- Programs can be configured to execute in ROM
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
- ``when`` statement to avoid if-else chains
- ``on .. goto`` statement for fast jump tables
- ``in`` expression for concise and efficient multi-value/containment test
- ``defer`` statement to help write concise and robust subroutine cleanup logic
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``

View File

@@ -744,9 +744,30 @@ An example, to select the number of cards to use depending on what game is playe
numcards = 52
on .. goto / on .. call statement (jump table)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
when statement ('jump table')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``on goto / call`` statement is suitable to create a fast call of a subroutine from a list based on an index value.
it selects a function to jump to in O(1) whereas a similar when-statement, runs in O(n) because that one checks each index value.
The ``on goto / call`` instead simply gets the correct entry from an array of function pointers and jumps to it directly.
The index value that is used is 0-based; 0 will jump to the first entry in the list, 1 to the second, and so on.
If the value is too large (i.e. outside the list of functions), no call is performed, and execution continues.
In this case you can optionally add an ``else`` block that is then executed instead. Here's an example::
on math.randrange(5) call (
routines.func1,
routines.func2,
routines.func3 )
else {
txt.print("no call was done")
}
on math.randrange(5) goto (routines.func1, routines.func2, routines.func3)
txt.print("no jump was taken")
when statement
^^^^^^^^^^^^^^
Instead of writing a bunch of sequential if-elseif statements, it is more readable to
use a ``when`` statement. (It will also result in greatly improved assembly code generation)
@@ -767,7 +788,8 @@ datatype as the when-value, otherwise no efficient comparison can be done.
You can explicitly put a list of numbers that all should result in the same case,
or even use any *range expression* as long as it denotes a constant list of numbers.
Be aware that every number is compared individually so using long lists of numbers and/or
many choice cases will result in poor performance.
many choice cases will result in poor performance. If you need to call a certain function
based on some sequential index number, look at the ``on .. goto / call`` statement instead.
Choices can result in a single statement or a block of multiple statements in which
case you have to use { } to enclose them.
@@ -1231,7 +1253,7 @@ just before exiting of the current subroutine. That can be via a return statemen
or just the normal ending of the subroutine. This is often useful to "not forget" to clean up stuff,
and if the subroutine has multiple ways or places where it can exit, it saves you from repeating
the cleanup code at every exit spot. Multiple defers can be scheduled in a single subroutine (up to a maximum of 8).
They are handled in reversed order. Return values are evaluated before any deferred code is executed.
The defers are handled in reversed (LIFO) order. Return values are evaluated before any deferred code is executed.
You write defers like so::
sub example() -> bool {

View File

@@ -54,6 +54,17 @@ Various things:
A Prog8 library module that provides Commander X16 style RAM banking on a C64 with an REU.
This module provides cx16.rambank(), x16jsrfar() and extsub @bank functionality on a C64.
`Library blob link example <https://github.com/FearLabsAudio/Prog8_blobLink_example/>`_
An example of a simple utility that can link symbols in a main Prog8 program
so that they are accessable from an externally loaded library blob.
It pre-processes the debug symbols list file at compile time,
and substitutes references in a template module file.
`XLink: an alternative library blob link example <https://github.com/gillham/X16/tree/main/xlink>`_
This is another approach to access routines from a banked loaded library,
and it does it at run time. In this demo a jump table is not only created in the library,
but also in the main program and copied into the library for its use.
.. image:: _static/curious.png
:align: center

View File

@@ -64,6 +64,7 @@ Future Things and Ideas
- fix the line, cols in Position, sometimes they count from 0 sometimes from 1
- is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
- enums?
- romable: should we have a way to explicitly set the memory address for the BSS area (instead of only the highram bank number on X16, allow a memory address too for the -varshigh option?)
- Kotlin: can we use inline value classes in certain spots?
- add float support to the configurable compiler targets
@@ -100,6 +101,7 @@ IR/VM
cx16.r0sL = cx16.r1s as byte }
- do something with the 'split' tag on split word arrays
- add more optimizations in IRPeepholeOptimizer
- apparently for SSA form, the IRCodeChunk is not a proper "basic block" yet because the last operation should be a branch or return, and no other branches
- reduce register usage via linear-scan algorithm (based on live intervals) https://anoopsarkar.github.io/compilers-class/assets/lectures/opt3-regalloc-linearscan.pdf
don't forget to take into account the data type of the register when it's going to be reused!
- idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
@@ -123,11 +125,11 @@ Libraries
Optimizations
-------------
- when choices that are only a goto -> avoid the double branch, can branch to the label directly
- Sorting module gnomesort_uw could be optimized more by fully rewriting it in asm? Shellshort seems consistently faster even if most of the words are already sorted.
- can the for loop temp var be replaced by the same logic that createRepeatCounterVar() uses for repeat loops? Take care of nested for/repeat loops to not use the same var
- Compare output of some Oscar64 samples to what prog8 does for the equivalent code (see https://github.com/drmortalwombat/OscarTutorials/tree/main and https://github.com/drmortalwombat/oscar64/tree/main/samples)
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code,
those checks should probably be removed, or be made permanent
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code, those checks should probably be removed, or be made permanent

View File

@@ -412,7 +412,7 @@ change it for the whole file at once. Here are examples of the possible encoding
- ``iso5:"Хозяин и Работник"`` string in iso-8859-5 encoding (Cyrillic)
- ``iso16:"zażółć gęślą jaźń"`` string in iso-8859-16 encoding (Eastern Europe)
- ``atascii:"I am Atari!"`` string in "atascii" encoding (Atari 8-bit)
- ``cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"`` string in "cp437" encoding (IBM PC codepage 437)
- ``cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"`` string in "cp437" encoding (IBM PC codepage 437) See note below!
- ``kata:"アノ ニホンジン ワ ガイコクジン。 # が # ガ"`` string in "kata" encoding (Katakana)
- ``c64os:"^Hello_World! \\ ~{_}~"`` string in "c64os" encoding (C64 OS)
@@ -490,6 +490,13 @@ for a character in such strings (one that stops at the first 0 byte)
of such a string without destroying other occurrences (as long as you stay within
the size of the allocated string!)
.. note:: printing **cp437** encoded strings
To print strings in the **cp437** encoding, you will probably need ``txt.print_lit(message)`` to properly print
them to the screen. This is because this encoding has symbols in place of where normally ASCII
control characters such as Line feed would be. A regular ``txt.print(message)`` will likely get confused
by such symbols and print them as control characters, messing up the output.
.. _range-expression:

View File

@@ -192,7 +192,7 @@ class IRProgram(val name: String,
blocks.forEach { block ->
block.children.forEachIndexed { index, child ->
val next = if(index<block.children.size-1) block.children[index+1] as? IRCodeChunkBase else null
val next = if(index<block.children.lastIndex) block.children[index+1] as? IRCodeChunkBase else null
when (child) {
is IRAsmSubroutine -> child.asmChunk.next = next
is IRCodeChunk -> child.next = next

View File

@@ -24,6 +24,12 @@ BLOCK_COMMENT : '/*' ( BLOCK_COMMENT | . )*? '*/' -> skip ;
WS : [ \t] -> skip ;
// WS2 : '\\' EOL -> skip;
VOID: 'void';
ON: 'on';
GOTO: 'goto';
CALL: 'call';
INLINE: 'inline';
ELSE: 'else';
UNICODEDNAME : [\p{Letter}][\p{Letter}\p{Mark}\p{Digit}_]* ; // match unicode properties
UNDERSCORENAME : '_' UNICODEDNAME ; // match unicode properties
DEC_INTEGER : DEC_DIGIT (DEC_DIGIT | '_')* ;
@@ -109,6 +115,7 @@ statement :
| breakstmt
| continuestmt
| labeldef
| ongoto
| defer
| alias
;
@@ -141,7 +148,7 @@ defer: 'defer' (statement | statement_block) ;
labeldef : identifier ':' ;
unconditionaljump : 'goto' expression ;
unconditionaljump : GOTO expression ;
directive : directivename (directivenamelist | (directivearg? | directivearg (',' directivearg)*)) ;
@@ -181,9 +188,11 @@ assign_target:
| directmemory #MemoryTarget
| pointerdereference #PointerDereferenceTarget
| pointerindexedderef #PointerIndexedDerefTarget
| void #VoidTarget
| voidtarget #VoidTarget
;
voidtarget : VOID ;
multi_assign_target:
assign_target (',' assign_target)+ ;
@@ -225,8 +234,6 @@ arrayindexed:
;
void : VOID ;
typecast : 'as' datatype;
directmemory : '@' '(' expression ')';
@@ -249,7 +256,7 @@ breakstmt : 'break';
continuestmt: 'continue';
identifier : UNICODEDNAME | UNDERSCORENAME ;
identifier : UNICODEDNAME | UNDERSCORENAME | ON | CALL | INLINE ; // due to the way antlr creates tokens, need to list the tokens here explicitly that we want to allow as identifiers too
scoped_identifier : identifier ('.' identifier)* ;
@@ -277,8 +284,6 @@ literalvalue :
inlineasm : directivename EOL? INLINEASMBLOCK; // directive name should be '%asm' or '%ir'
inline: 'inline';
subroutine :
'sub' identifier '(' sub_params? ')' sub_return_part? EOL? (statement_block EOL?)
;
@@ -297,7 +302,7 @@ sub_params : sub_param (',' EOL? sub_param)* ;
sub_param: vardecl ('@' register=UNICODEDNAME)? ;
asmsubroutine :
inline? 'asmsub' asmsub_decl EOL? (statement_block EOL?)
INLINE? 'asmsub' asmsub_decl EOL? (statement_block EOL?)
;
extsubroutine :
@@ -321,9 +326,9 @@ asmsub_return : datatype '@' register=UNICODEDNAME ; // A,X,Y,AX,AY,XY,Pc,P
if_stmt : 'if' expression EOL? (statement | statement_block) EOL? else_part? ; // statement is constrained later
else_part : 'else' EOL? (statement | statement_block) ; // statement is constrained later
else_part : ELSE EOL? (statement | statement_block) ; // statement is constrained later
if_expression : 'if' expression EOL? expression EOL? 'else' EOL? expression ;
if_expression : 'if' expression EOL? expression EOL? ELSE EOL? expression ;
// This is a cursed mix of IdentifierReference (scoped identifiers) and binary expressions with '.' dereference operators.
// but it is needed for now to not have to rewrite all of Prog8's dependence on how the IdentifierReference now works (fully qualified identifier string inside)
@@ -356,4 +361,6 @@ unrollloop: 'unroll' expression EOL? (statement | statement_block) ; // no
whenstmt: 'when' expression EOL? '{' EOL? (when_choice | EOL) * '}' EOL? ;
when_choice: (expression_list | 'else' ) '->' (statement | statement_block ) ;
when_choice: (expression_list | ELSE ) '->' (statement | statement_block ) ;
ongoto: ON expression kind=(GOTO | CALL) directivenamelist EOL? else_part? ;

View File

@@ -79,9 +79,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
else
"%asm {{ ...${node.assembly.length} characters... }}"
}
is PtJump -> {
"goto ${txt(node.target)}"
}
is PtJump -> "goto"
is PtAsmSub -> {
val params = node.parameters.joinToString(", ") {
val register = it.first.registerOrPair