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 - high-level program optimizations
- conditional branches that map 1:1 to cpu status flags - conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains - ``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 - ``in`` expression for concise and efficient multi-value/containment check
- ``defer`` statement to help write concise and robust subroutine cleanup logic - ``defer`` statement to help write concise and robust subroutine cleanup logic
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror`` - 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 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 name = NAME
override val defaultEncoding = Encoding.PETSCII override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null 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_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
override val BSSHIGHRAM_START = 0u // TODO override val BSSHIGHRAM_START = 0u // TODO address?
override val BSSHIGHRAM_END = 0u // TODO override val BSSHIGHRAM_END = 0u // TODO address?
override val BSSGOLDENRAM_START = 0u // TODO override val BSSGOLDENRAM_START = 0u // TODO address?
override val BSSGOLDENRAM_END = 0u // TODO override val BSSGOLDENRAM_END = 0u // TODO address?
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam override lateinit var golden: GoldenRam

View File

@@ -7,7 +7,10 @@ import java.io.IOException
import java.nio.file.Path 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 name = NAME
override val defaultEncoding = Encoding.PETSCII override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null override val libraryPath = null

View File

@@ -37,7 +37,7 @@ class ConfigFileTarget(
val zpFullsafe: List<UIntRange>, val zpFullsafe: List<UIntRange>,
val zpKernalsafe: List<UIntRange>, val zpKernalsafe: List<UIntRange>,
val zpBasicsafe: 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 { companion object {

View File

@@ -6,7 +6,10 @@ import prog8.code.target.zp.CX16Zeropage
import java.nio.file.Path 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 name = NAME
override val defaultEncoding = Encoding.PETSCII override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null override val libraryPath = null

View File

@@ -6,7 +6,10 @@ import prog8.code.target.zp.PETZeropage
import java.nio.file.Path 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 name = NAME
override val defaultEncoding = Encoding.PETSCII override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null override val libraryPath = null

View File

@@ -7,7 +7,10 @@ import kotlin.io.path.isReadable
import kotlin.io.path.name import kotlin.io.path.name
import kotlin.io.path.readText 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 name = NAME
override val defaultEncoding = Encoding.ISO override val defaultEncoding = Encoding.ISO
override val libraryPath = null override val libraryPath = null

View File

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

View File

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

View File

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

View File

@@ -6,14 +6,16 @@ import com.github.michaelbull.result.Result
import java.io.CharConversionException import java.io.CharConversionException
import java.nio.charset.Charset import java.nio.charset.Charset
open class IsoEncodingBase(charsetName: String) { open class IsoEncodingBase(charsetName: String) {
val charset: Charset = Charset.forName(charsetName) 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 { return try {
val mapped = str.map { chr -> val mapped = str.map { chr ->
when (chr) { when (chr) {
'\u0000' -> 0u '\u0000' -> 0u
'\n' -> if(newlineToCarriageReturn) 13u else 10u
in '\u8000'..'\u80ff' -> { in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly // special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte() (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 { 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) { } catch (ce: CharConversionException) {
Err(ce) Err(ce)
} }

View File

@@ -64,10 +64,11 @@ object JapaneseCharacterConverter {
object KatakanaEncoding { object KatakanaEncoding {
val charset: Charset = Charset.forName("JIS_X0201") 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 { return try {
val mapped = str.map { chr -> val mapped = str.map { chr ->
when (chr) { when (chr) {
'\n' -> if(newlineToCarriageReturn) 13u else 10u
'\u0000' -> 0u '\u0000' -> 0u
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves '\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 { 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) { } catch (ce: CharConversionException) {
Err(ce) Err(ce)
} }

View File

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

View File

@@ -1034,6 +1034,7 @@ $repeatLabel""")
val target = getJumpTarget(jump) val target = getJumpTarget(jump)
require(!target.needsExpressionEvaluation) require(!target.needsExpressionEvaluation)
if(target.indirect) { if(target.indirect) {
require(!target.indexedX)
val complementedInstruction = branchInstruction(stmt.condition, true) val complementedInstruction = branchInstruction(stmt.condition, true)
out(""" out("""
$complementedInstruction + $complementedInstruction +
@@ -1085,18 +1086,24 @@ $repeatLabel""")
else { else {
if(evaluateAddressExpression) { if(evaluateAddressExpression) {
val arrayIdx = jump.target as? PtArrayIndexer val arrayIdx = jump.target as? PtArrayIndexer
if (isTargetCpu(CpuType.CPU65C02) && arrayIdx!=null) { if (arrayIdx!=null) {
if (!arrayIdx.splitWords) { if (isTargetCpu(CpuType.CPU65C02)) {
// if the jump target is an address in a non-split array (like a jump table of only pointers), if (!arrayIdx.splitWords) {
// on the 65c02, more optimal assembly can be generated using JMP address,X // if the jump target is an address in a non-split array (like a jump table of only pointers),
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A) // on the 65c02, more optimal assembly can be generated using JMP (address,X)
out(" asl a | tax") assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
return JumpTarget(asmSymbolName(arrayIdx.variable), true, true, false) 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 { } else {
// print a message when more optimal code is possible // print a message when more optimal code is possible for 6502 cpu
val variable = symbolTable.lookup(arrayIdx.variable.name)!! if(!arrayIdx.splitWords)
if(variable is StStaticVariable && variable.length!!<=128u) errors.info("the jump address array is @nosplit, but @split would create more efficient code here", jump.position)
errors.info("the jump address array is @split, but @nosplit would create more efficient code here", jump.position)
} }
} }
// we can do the address evaluation right now and just use a temporary pointer variable // 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 val identifier = identMatch.value
when (val symbol = symbolTable.flat[identifier]) { when (val symbol = symbolTable.flat[identifier]) {
is StConstant -> symbol.value.toUInt() is StConstant -> symbol.value.toUInt()
is StMemVar -> symbol.address.toUInt() is StMemVar -> symbol.address
else -> null else -> null
} }
} else null } else null
@@ -512,6 +512,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
// rts + jmp -> remove jmp // rts + jmp -> remove jmp
// rts + bxx -> remove bxx // rts + bxx -> remove bxx
// lda + cmp #0 -> remove cmp, same for cpy and cpx. // lda + cmp #0 -> remove cmp, same for cpy and cpx.
// bra/jmp + bra/jmp -> remove second bra/jmp
// and some other optimizations. // and some other optimizations.
val mods = mutableListOf<Modification>() 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(testForBitSet) {
if(jumpAfterIf!=null) { if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf) val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bmi", target) branch("bmi", target)
} }
else else
@@ -93,6 +94,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else { } else {
if(jumpAfterIf!=null) { if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf) val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bpl", target) branch("bpl", target)
} }
else else
@@ -106,6 +108,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
if(testForBitSet) { if(testForBitSet) {
if(jumpAfterIf!=null) { if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf) val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvs", target) branch("bvs", target)
} }
else else
@@ -113,6 +116,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
} else { } else {
if(jumpAfterIf!=null) { if(jumpAfterIf!=null) {
val target = asmgen.getJumpTarget(jumpAfterIf) val target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
branch("bvc", target) branch("bvc", target)
} }
else else
@@ -171,9 +175,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" $falseBranch +") asmgen.out(" $falseBranch +")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
asmgen.out(""" asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
jmp (${target.asmLabel}) asmgen.out("+")
+""")
} else { } else {
require(!target.needsExpressionEvaluation) require(!target.needsExpressionEvaluation)
asmgen.out(" $branchInstr ${target.asmLabel}") asmgen.out(" $branchInstr ${target.asmLabel}")
@@ -286,6 +289,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" bmi + | beq +") asmgen.out(" bmi + | beq +")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf) target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
jmp (${target.asmLabel}) jmp (${target.asmLabel})
+""") +""")
@@ -353,6 +357,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" bmi + | bne ++") asmgen.out(" bmi + | bne ++")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf) target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
+ jmp (${target.asmLabel}) + jmp (${target.asmLabel})
+""") +""")
@@ -434,6 +439,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" bcc + | beq +") asmgen.out(" bcc + | beq +")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jumpAfterIf) target = asmgen.getJumpTarget(jumpAfterIf)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
jmp (${target.asmLabel}) jmp (${target.asmLabel})
+""") +""")
@@ -535,6 +541,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
+ bpl +""") + bpl +""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
jmp (${target.asmLabel}) jmp (${target.asmLabel})
+""") +""")
@@ -590,6 +597,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
bcs +""") bcs +""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
_jump jmp (${target.asmLabel}) _jump jmp (${target.asmLabel})
+""") +""")
@@ -667,6 +675,7 @@ _jump jmp (${target.asmLabel})
+ bpl +""") + bpl +""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
jmp (${target.asmLabel}) jmp (${target.asmLabel})
+""") +""")
@@ -721,6 +730,7 @@ _jump jmp (${target.asmLabel})
bcc +""") bcc +""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
jmp (${target.asmLabel}) jmp (${target.asmLabel})
+""") +""")
@@ -829,6 +839,7 @@ _jump jmp (${target.asmLabel})
bne ++""") bne ++""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
+ jmp (${target.asmLabel}) + jmp (${target.asmLabel})
+""") +""")
@@ -908,6 +919,7 @@ _jump jmp (${target.asmLabel})
bne ++""") bne ++""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
+ jmp (${target.asmLabel}) + jmp (${target.asmLabel})
+""") +""")
@@ -973,6 +985,7 @@ _jump jmp (${target.asmLabel})
beq ++""") beq ++""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
+ jmp (${target.asmLabel}) + jmp (${target.asmLabel})
+""") +""")
@@ -1053,6 +1066,7 @@ _jump jmp (${target.asmLabel})
beq ++""") beq ++""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
+ jmp (${target.asmLabel}) + jmp (${target.asmLabel})
+""") +""")
@@ -1196,6 +1210,7 @@ _jump jmp (${target.asmLabel})
beq ++""") beq ++""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
+ jmp (${target.asmLabel}) + jmp (${target.asmLabel})
+""") +""")
@@ -1248,6 +1263,7 @@ _jump jmp (${target.asmLabel})
bne +""") bne +""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
jmp (${target.asmLabel}) jmp (${target.asmLabel})
+""") +""")
@@ -1302,6 +1318,7 @@ _jump jmp (${target.asmLabel})
beq ++""") beq ++""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
+ jmp (${target.asmLabel}) + jmp (${target.asmLabel})
+""") +""")
@@ -1361,6 +1378,7 @@ _jump jmp (${target.asmLabel})
bne +""") bne +""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
jmp (${target.asmLabel}) jmp (${target.asmLabel})
+""") +""")
@@ -1423,6 +1441,7 @@ _jump jmp (${target.asmLabel})
beq ++""") beq ++""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
+ jmp (${target.asmLabel}) + jmp (${target.asmLabel})
+""") +""")
@@ -1481,6 +1500,7 @@ _jump jmp (${target.asmLabel})
bne +""") bne +""")
if(target.needsExpressionEvaluation) if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump) target = asmgen.getJumpTarget(jump)
require(!target.indexedX)
asmgen.out(""" asmgen.out("""
jmp (${target.asmLabel}) jmp (${target.asmLabel})
+""") +""")

View File

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

View File

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

View File

@@ -2356,6 +2356,12 @@ internal class AstChecker(private val program: Program,
return false 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) { 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() caster.applyModifications()
} }
fun Program.desugaring(errors: IErrorReporter) { fun Program.desugaring(errors: IErrorReporter, options: CompilationOptions) {
val desugar = CodeDesugarer(this, errors) val desugar = CodeDesugarer(this, options.compTarget, errors)
desugar.visit(this) desugar.visit(this)
while(errors.noErrors() && desugar.applyModifications()>0) while(errors.noErrors() && desugar.applyModifications()>0)
desugar.visit(this) desugar.visit(this)

View File

@@ -8,7 +8,7 @@ import prog8.ast.walk.IAstModification
import prog8.code.core.* 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. // Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer ! // 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 // - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
// - flatten chained assignments // - flatten chained assignments
// - remove alias nodes // - 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) // - 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> { override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
@@ -394,4 +395,60 @@ _after:
} }
return noModifications 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 StructDecl -> transform(statement)
is When -> transform(statement) is When -> transform(statement)
is WhileLoop -> throw FatalAstException("while loops must have been converted to jumps") 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") 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 return cast
} }
private fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> { private fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (SourceCode.isLibraryResource(filename)) { return if (SourceCode.isLibraryResource(filename)) {
return com.github.michaelbull.result.runCatching { 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.core.unescape
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.code.target.Cx16Target import prog8.code.target.Cx16Target
import prog8.code.target.encodings.Encoder import prog8.code.target.encodings.*
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 prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
import java.io.CharConversionException import java.io.CharConversionException
@@ -227,7 +223,7 @@ class TestStringEncodings: FunSpec({
context("iso") { context("iso") {
test("iso accepts iso-characters") { 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) 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[1] shouldBe '\u801b'
passthrough[2] shouldBe '\u8099' passthrough[2] shouldBe '\u8099'
passthrough[3] shouldBe '\u80ff' 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 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 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 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) 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 val char2 = (main.statements[5] as Assignment).value as NumericLiteral
char2.number shouldBe 80.0 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(Cx16Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
compileText(VMTarget(), 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(".")}") 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) { override fun visit(deref: PtrDereference) {
output("${deref.identifier.nameInSource.joinToString(".")}^^") output("${deref.identifier.nameInSource.joinToString(".")}^^")
deref.chain.forEach { deref.chain.forEach {

View File

@@ -169,6 +169,9 @@ private fun StatementContext.toAst() : Statement {
val aliasstmt = alias()?.toAst() val aliasstmt = alias()?.toAst()
if(aliasstmt!=null) return aliasstmt if(aliasstmt!=null) return aliasstmt
val ongotostmt = ongoto()?.toAst()
if(ongotostmt!=null) return ongotostmt
val structdecl = structdeclaration()?.toAst() val structdecl = structdeclaration()?.toAst()
if(structdecl!=null) return structdecl if(structdecl!=null) return structdecl
@@ -176,7 +179,7 @@ private fun StatementContext.toAst() : Statement {
} }
private fun AsmsubroutineContext.toAst(): Subroutine { private fun AsmsubroutineContext.toAst(): Subroutine {
val inline = this.inline()!=null val inline = this.INLINE()!=null
val subdecl = asmsub_decl().toAst() val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf() val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(), 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() val identifiers = vardecl.identifierlist()?.identifier() ?: emptyList()
if(identifiers.size>1) if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition()) 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()) 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?> { private fun parseParamRegister(registerTok: Token?, pos: Position): Pair<RegisterOrPair?, Statusflag?> {
if(registerTok==null) if(registerTok==null)
@@ -354,7 +361,7 @@ private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
val identifiers = decl.identifierlist()?.identifier() ?: emptyList() val identifiers = decl.identifierlist()?.identifier() ?: emptyList()
if(identifiers.size>1) if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition()) 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()) val (registerorpair, statusregister) = parseParamRegister(it.register, it.toPosition())
if(statusregister!=null) { if(statusregister!=null) {
@@ -401,7 +408,7 @@ private fun Assign_targetContext.toAst() : AssignTarget {
AssignTarget(null, arrayindexed, null, null, false, position = toPosition()) AssignTarget(null, arrayindexed, null, null, false, position = toPosition())
} }
is VoidTargetContext -> { is VoidTargetContext -> {
AssignTarget(null, null, null, null, true, position = void_().toPosition()) AssignTarget(null, null, null, null, true, position = voidtarget().toPosition())
} }
is PointerDereferenceTargetContext -> { is PointerDereferenceTargetContext -> {
val deref = this.pointerdereference().toAst() val deref = this.pointerdereference().toAst()
@@ -844,7 +851,7 @@ private fun When_choiceContext.toAst(): WhenChoice {
private fun StructfielddeclContext.toAst(): Pair<DataType, List<String>> { private fun StructfielddeclContext.toAst(): Pair<DataType, List<String>> {
val identifiers = identifierlist()?.identifier() ?: emptyList() val identifiers = identifierlist()?.identifier() ?: emptyList()
val dt = datatype().toAst() 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 { 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 zp = getZpOption(tags)
val split = getSplitOption(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 name = if(identifiers.size==1) identifiers[0] else "<multiple>"
val isArray = ARRAYSIG() != null || arrayindex() != null val isArray = ARRAYSIG() != null || arrayindex() != null
val alignword = "@alignword" in tags val alignword = "@alignword" in tags
@@ -894,3 +901,12 @@ private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl
toPosition() 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}" val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
var arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") } var arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") }
if(arrayDt.isSplitWordArray) { 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) { when(arrayDt.sub) {
BaseDataType.WORD -> arrayDt = DataType.arrayFor(BaseDataType.WORD, false) BaseDataType.WORD -> arrayDt = DataType.arrayFor(BaseDataType.WORD, false)
BaseDataType.UWORD -> arrayDt = DataType.arrayFor(BaseDataType.UWORD, false) BaseDataType.UWORD -> arrayDt = DataType.arrayFor(BaseDataType.UWORD, false)
@@ -296,6 +296,24 @@ class VarDecl(val type: VarDeclType,
SplitWish.NOSPLIT, arraysize, autoVarName, emptyList(), array, SplitWish.NOSPLIT, arraysize, autoVarName, emptyList(), array,
sharedWithAsm = false, alignment = 0u, dirty = false, position = array.position) 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 val isArray: Boolean
@@ -1304,3 +1322,29 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
override fun copy() = DirectMemoryWrite(addressExpression.copy(), position) override fun copy() = DirectMemoryWrite(addressExpression.copy(), position)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = addressExpression.referencesIdentifier(nameInSource) 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(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whenStmt: When, 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(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(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
open fun after(array: ArrayLiteral, 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(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whenStmt: When, 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(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>>() protected val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
@@ -523,6 +525,14 @@ abstract class AstWalker {
track(after(chainedAssignment, parent), chainedAssignment, parent) 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) { fun visit(deref: PtrDereference, parent: Node) {
track(before(deref, parent), deref, parent) track(before(deref, parent), deref, parent)
deref.identifier.accept(this, deref) deref.identifier.accept(this, deref)

View File

@@ -208,6 +208,12 @@ interface IAstVisitor {
chainedAssignment.nested.accept(this) 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) { fun visit(deref: PtrDereference) {
deref.identifier.accept(this) deref.identifier.accept(this)
} }

View File

@@ -101,6 +101,7 @@ Features
- Programs can be configured to execute in ROM - Programs can be configured to execute in ROM
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency - Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
- ``when`` statement to avoid if-else chains - ``when`` statement to avoid if-else chains
- ``on .. goto`` statement for fast jump tables
- ``in`` expression for concise and efficient multi-value/containment test - ``in`` expression for concise and efficient multi-value/containment test
- ``defer`` statement to help write concise and robust subroutine cleanup logic - ``defer`` statement to help write concise and robust subroutine cleanup logic
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror`` - 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 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 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) 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, 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. 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 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 Choices can result in a single statement or a block of multiple statements in which
case you have to use { } to enclose them. 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, 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 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). 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:: You write defers like so::
sub example() -> bool { 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. 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. 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 .. image:: _static/curious.png
:align: center :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 - 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" ? - 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?) - 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? - Kotlin: can we use inline value classes in certain spots?
- add float support to the configurable compiler targets - add float support to the configurable compiler targets
@@ -100,6 +101,7 @@ IR/VM
cx16.r0sL = cx16.r1s as byte } cx16.r0sL = cx16.r1s as byte }
- do something with the 'split' tag on split word arrays - do something with the 'split' tag on split word arrays
- add more optimizations in IRPeepholeOptimizer - 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 - 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! 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) - 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 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. - 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 - 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) - 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" - 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? - 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 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, - 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
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) - ``iso5:"Хозяин и Работник"`` string in iso-8859-5 encoding (Cyrillic)
- ``iso16:"zażółć gęślą jaźń"`` string in iso-8859-16 encoding (Eastern Europe) - ``iso16:"zażółć gęślą jaźń"`` string in iso-8859-16 encoding (Eastern Europe)
- ``atascii:"I am Atari!"`` string in "atascii" encoding (Atari 8-bit) - ``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) - ``kata:"アノ ニホンジン ワ ガイコクジン。 # が # ガ"`` string in "kata" encoding (Katakana)
- ``c64os:"^Hello_World! \\ ~{_}~"`` string in "c64os" encoding (C64 OS) - ``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 of such a string without destroying other occurrences (as long as you stay within
the size of the allocated string!) 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: .. _range-expression:

View File

@@ -192,7 +192,7 @@ class IRProgram(val name: String,
blocks.forEach { block -> blocks.forEach { block ->
block.children.forEachIndexed { index, child -> 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) { when (child) {
is IRAsmSubroutine -> child.asmChunk.next = next is IRAsmSubroutine -> child.asmChunk.next = next
is IRCodeChunk -> child.next = next is IRCodeChunk -> child.next = next

View File

@@ -24,6 +24,12 @@ BLOCK_COMMENT : '/*' ( BLOCK_COMMENT | . )*? '*/' -> skip ;
WS : [ \t] -> skip ; WS : [ \t] -> skip ;
// WS2 : '\\' EOL -> skip; // WS2 : '\\' EOL -> skip;
VOID: 'void'; VOID: 'void';
ON: 'on';
GOTO: 'goto';
CALL: 'call';
INLINE: 'inline';
ELSE: 'else';
UNICODEDNAME : [\p{Letter}][\p{Letter}\p{Mark}\p{Digit}_]* ; // match unicode properties UNICODEDNAME : [\p{Letter}][\p{Letter}\p{Mark}\p{Digit}_]* ; // match unicode properties
UNDERSCORENAME : '_' UNICODEDNAME ; // match unicode properties UNDERSCORENAME : '_' UNICODEDNAME ; // match unicode properties
DEC_INTEGER : DEC_DIGIT (DEC_DIGIT | '_')* ; DEC_INTEGER : DEC_DIGIT (DEC_DIGIT | '_')* ;
@@ -109,6 +115,7 @@ statement :
| breakstmt | breakstmt
| continuestmt | continuestmt
| labeldef | labeldef
| ongoto
| defer | defer
| alias | alias
; ;
@@ -141,7 +148,7 @@ defer: 'defer' (statement | statement_block) ;
labeldef : identifier ':' ; labeldef : identifier ':' ;
unconditionaljump : 'goto' expression ; unconditionaljump : GOTO expression ;
directive : directivename (directivenamelist | (directivearg? | directivearg (',' directivearg)*)) ; directive : directivename (directivenamelist | (directivearg? | directivearg (',' directivearg)*)) ;
@@ -181,9 +188,11 @@ assign_target:
| directmemory #MemoryTarget | directmemory #MemoryTarget
| pointerdereference #PointerDereferenceTarget | pointerdereference #PointerDereferenceTarget
| pointerindexedderef #PointerIndexedDerefTarget | pointerindexedderef #PointerIndexedDerefTarget
| void #VoidTarget | voidtarget #VoidTarget
; ;
voidtarget : VOID ;
multi_assign_target: multi_assign_target:
assign_target (',' assign_target)+ ; assign_target (',' assign_target)+ ;
@@ -225,8 +234,6 @@ arrayindexed:
; ;
void : VOID ;
typecast : 'as' datatype; typecast : 'as' datatype;
directmemory : '@' '(' expression ')'; directmemory : '@' '(' expression ')';
@@ -249,7 +256,7 @@ breakstmt : 'break';
continuestmt: 'continue'; 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)* ; scoped_identifier : identifier ('.' identifier)* ;
@@ -277,8 +284,6 @@ literalvalue :
inlineasm : directivename EOL? INLINEASMBLOCK; // directive name should be '%asm' or '%ir' inlineasm : directivename EOL? INLINEASMBLOCK; // directive name should be '%asm' or '%ir'
inline: 'inline';
subroutine : subroutine :
'sub' identifier '(' sub_params? ')' sub_return_part? EOL? (statement_block EOL?) '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)? ; sub_param: vardecl ('@' register=UNICODEDNAME)? ;
asmsubroutine : asmsubroutine :
inline? 'asmsub' asmsub_decl EOL? (statement_block EOL?) INLINE? 'asmsub' asmsub_decl EOL? (statement_block EOL?)
; ;
extsubroutine : 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 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. // 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) // 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? ; 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 else
"%asm {{ ...${node.assembly.length} characters... }}" "%asm {{ ...${node.assembly.length} characters... }}"
} }
is PtJump -> { is PtJump -> "goto"
"goto ${txt(node.target)}"
}
is PtAsmSub -> { is PtAsmSub -> {
val params = node.parameters.joinToString(", ") { val params = node.parameters.joinToString(", ") {
val register = it.first.registerOrPair val register = it.first.registerOrPair