mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Merge branch 'v12maintenance'
# Conflicts: # codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt # compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt # compiler/test/ast/TestVariousCompilerAst.kt # compiler/test/codegeneration/TestExecution6502.kt # examples/test.p8 # gradle.properties
This commit is contained in:
commit
dbba6a94fa
13 files changed
+351
-89
No files matched your search
@@ -1504,7 +1504,7 @@ $repeatLabel""")
|
||||
}
|
||||
else if(swap.target1.pointerDeref!=null && swap.target2.pointerDeref!=null) {
|
||||
val v1 = swap.target1.pointerDeref!!
|
||||
val v2 = swap.target1.pointerDeref!!
|
||||
val v2 = swap.target2.pointerDeref!!
|
||||
if(v1.derefLast && v1.chain.isEmpty() && v2.derefLast && v2.chain.isEmpty() && isZpVar(v1.startpointer) && isZpVar(v2.startpointer)) {
|
||||
// optimized case where v1 and v2 are both already zeropage pointer variables
|
||||
val name1 = asmVariableName(v1.startpointer)
|
||||
|
||||
@@ -33,8 +33,8 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return when (fcall.name) {
|
||||
"msw" -> funcMsw(fcall, firstReturnRegister ?: RegisterOrPair.AY)
|
||||
"lsw" -> funcLsw(fcall, firstReturnRegister ?: RegisterOrPair.AY)
|
||||
"msb" -> funcMsb(fcall, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"msb__long" -> funcMsbLong(fcall, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"msb" -> funcMsb(fcall, false, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"msb__long" -> funcMsb(fcall, true, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"lsb" -> funcLsb(fcall, false, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"lsb__long" -> funcLsb(fcall,true, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"mkword" -> funcMkword(fcall, firstReturnRegister ?: RegisterOrPair.AY)
|
||||
@@ -2527,55 +2527,36 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return arrayOf(RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
private fun funcMsbLong(fcall: PtFunctionCall, resultReg: RegisterOrPair): Array<RegisterOrPair> {
|
||||
private fun funcMsb(fcall: PtFunctionCall, fromLong: Boolean, resultReg: RegisterOrPair): Array<RegisterOrPair> {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.type.isLong)
|
||||
throw AssemblyError("msb__long requires long argument")
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName+3")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+3")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+3")
|
||||
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
|
||||
}
|
||||
} else if(arg is PtArrayIndexer) {
|
||||
TODO("msb of long array element ${fcall.position}")
|
||||
if(fromLong) {
|
||||
if (!arg.type.isLong)
|
||||
throw AssemblyError("msb__long requires long argument")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.R14R15, arg.type.isSigned)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda cx16.r14+3")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx cx16.r14+3")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy cx16.r14+3")
|
||||
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
|
||||
}
|
||||
if (!arg.type.isWord && !arg.type.isPointer)
|
||||
throw AssemblyError("msb requires word argument")
|
||||
}
|
||||
|
||||
return arrayOf(resultReg)
|
||||
}
|
||||
|
||||
private fun funcMsb(fcall: PtFunctionCall, resultReg: RegisterOrPair): Array<RegisterOrPair> {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.type.isWord)
|
||||
throw AssemblyError("msb requires word argument")
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
|
||||
val offset = if(fromLong) 3 else 1
|
||||
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+1 | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+1 | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
|
||||
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName+$offset")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+$offset")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+$offset")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+$offset | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+$offset | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+$offset | ldy #0")
|
||||
else -> throw AssemblyError("invalid register for msb${if(fromLong) " long" else ""}: $resultReg ${arg.position}")
|
||||
}
|
||||
} else {
|
||||
if(arg is PtArrayIndexer) {
|
||||
if(fromLong)
|
||||
TODO("msb of long array element ${fcall.position}")
|
||||
|
||||
// just read the msb byte out of the word array
|
||||
if(arg.splitWords) {
|
||||
if(arg.variable==null)
|
||||
@@ -2597,15 +2578,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar,y | ldx #0")
|
||||
}
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar,y | ldy #0")
|
||||
}
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
|
||||
asmgen.out(" ldy $arrayVar,x | ldx #0")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
|
||||
}
|
||||
} else {
|
||||
@@ -2641,6 +2622,14 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
|
||||
}
|
||||
}
|
||||
} else if(fromLong) {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.R14R15, arg.type.isSigned)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda cx16.r14+$offset")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx cx16.r14+$offset")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy cx16.r14+$offset")
|
||||
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
|
||||
}
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" tya")
|
||||
@@ -2655,7 +2644,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(fromLong) {
|
||||
if (!arg.type.isLong) throw AssemblyError("lsb__long requires long")
|
||||
} else {
|
||||
if (!arg.type.isWord) throw AssemblyError("lsb requires word")
|
||||
if (!arg.type.isWord && !arg.type.isPointer) throw AssemblyError("lsb requires word")
|
||||
}
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||
|
||||
@@ -382,7 +382,7 @@ internal class AssignmentAsmGen(
|
||||
throw AssemblyError("weird array element type for pointer indexed read $elementDt")
|
||||
}
|
||||
} else {
|
||||
val (zpPtrVar, _) = pointergen.deref(pointerDeref, addOffsetToPointer = true)
|
||||
val (zpPtrVar, _) = pointergen.deref(pointerDeref, addOffsetToPointer = true, loadPointerFieldValue = true)
|
||||
|
||||
when {
|
||||
elementDt.isByteOrBool -> {
|
||||
|
||||
@@ -62,6 +62,63 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModificationIndexedPointerField(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
|
||||
val array = target.array!!
|
||||
val elementDt = target.datatype
|
||||
if(!elementDt.isByteOrBool && !elementDt.isWord)
|
||||
throw AssemblyError("inplace modification indexed pointer field for type $elementDt not supported")
|
||||
val indexedTarget = IndexedPtrTarget(target)
|
||||
val ptr = ptrgen.indexedEffectiveAddress(indexedTarget)
|
||||
|
||||
val elementTemp = asmgen.createTempVarReused(elementDt.base, false, array)
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($ptr),y
|
||||
sta $elementTemp""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($ptr),y
|
||||
sta $elementTemp
|
||||
iny
|
||||
lda ($ptr),y
|
||||
sta $elementTemp+1""")
|
||||
}
|
||||
|
||||
// save the computed address on the hardware stack so later code can't clobber it
|
||||
asmgen.out("""
|
||||
lda $ptr
|
||||
pha
|
||||
lda $ptr+1
|
||||
pha""")
|
||||
|
||||
// reuse the existing variable-target logic to modify the temp
|
||||
val tempTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, elementDt, null, target.position, variableAsmName = elementTemp)
|
||||
inplaceModification(tempTarget, operator, value)
|
||||
|
||||
// restore the address and store the temp back
|
||||
asmgen.out("""
|
||||
pla
|
||||
sta $ptr+1
|
||||
pla
|
||||
sta $ptr""")
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $elementTemp
|
||||
sta ($ptr),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $elementTemp
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $elementTemp+1
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
|
||||
|
||||
// the asm-gen code can deal with situations where you want to assign a byte into a word.
|
||||
@@ -247,7 +304,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
TODO("inplace modification array indexed pointer deref ${target.position}")
|
||||
inplaceModificationIndexedPointerField(target, operator, value)
|
||||
return
|
||||
}
|
||||
val targetArrayVar = target.array.variable!!
|
||||
|
||||
@@ -230,10 +230,11 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false): Pair<String, UByte> {
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false, loadPointerFieldValue: Boolean=false): Pair<String, UByte> {
|
||||
// walk the pointer deref chain and leaves the final pointer value in a ZP var
|
||||
// this will often be the temp var P8ZP_SCRATCH_PTR but can also be the original pointer variable if it is already in zeropage and there is nothing to add to it
|
||||
// returns the ZP var to use as a pointer, and a Y register offset (which can be zero)
|
||||
// loadPointerFieldValue: for addOffsetToPointer=true, also load the value if the final field is a pointer-typed field (rather than returning its address).
|
||||
|
||||
fun addFieldOffsetToScratchPointer(fieldoffset: UInt) {
|
||||
if(fieldoffset==0u)
|
||||
@@ -278,7 +279,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
if(addOffsetToPointer) {
|
||||
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
addFieldOffsetToScratchPointer(field.second.toUInt())
|
||||
if(pointer.derefLast)
|
||||
if(pointer.derefLast || (loadPointerFieldValue && field.first.isPointer))
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
} else {
|
||||
@@ -328,6 +329,8 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
} else {
|
||||
if(addOffsetToPointer) {
|
||||
addFieldOffsetToScratchPointer(fieldinfo.second.toUInt())
|
||||
if(loadPointerFieldValue && fieldinfo.first.isPointer)
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
} else
|
||||
return "P8ZP_SCRATCH_PTR" to fieldinfo.second
|
||||
@@ -359,7 +362,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
// save scaled index to W2, compute base (field-corrected) into P8ZP_SCRATCH_PTR, then add
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | sta P8ZP_SCRATCH_W2 | lda P8ZP_SCRATCH_W1+1 | sta P8ZP_SCRATCH_W2+1")
|
||||
val (basePtr, _) = deref(target.pointer, addOffsetToPointer = true)
|
||||
val (basePtr, _) = deref(target.pointer, addOffsetToPointer = true, loadPointerFieldValue = true)
|
||||
if(basePtr != "P8ZP_SCRATCH_PTR") {
|
||||
// deref returned the original zp pointer variable; copy it so we don't mutate it
|
||||
asmgen.out("""
|
||||
@@ -452,7 +455,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
inplaceByteInc(target, amount)
|
||||
} else {
|
||||
if (target.dt.isByte) inplaceByteAdd(target, value)
|
||||
else if (target.dt.isWord) inplaceWordAdd(target, value)
|
||||
else if (target.dt.isWord || target.dt.isPointer) inplaceWordAdd(target, value)
|
||||
else if (target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
|
||||
else if (target.dt.isLong) inplaceLongAdd(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
@@ -464,7 +467,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
inplaceByteDec(target, amount)
|
||||
} else {
|
||||
if (target.dt.isByte) inplaceByteSub(target, value)
|
||||
else if (target.dt.isWord) inplaceWordSub(target, value)
|
||||
else if (target.dt.isWord || target.dt.isPointer) inplaceWordSub(target, value)
|
||||
else if (target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
|
||||
else if (target.dt.isLong) inplaceLongSub(target, value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
|
||||
@@ -318,6 +318,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
|
||||
codeGen.storeValueAtPointersLocation(inplaceInstrs, addressReg, fieldOffset, pointerDeref.type, false, oldvalueReg)
|
||||
chunks = inplaceInstrs
|
||||
} else if(array?.pointerderef != null) {
|
||||
chunks = fallbackAssign(augAssign)
|
||||
} else {
|
||||
chunks = when (augAssign.operator) {
|
||||
"+=" -> operatorPlusInplace(symbol, array, constAddress, memTarget, targetDt, value)
|
||||
|
||||
@@ -11,6 +11,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.parser.Prog8Keywords
|
||||
|
||||
/**
|
||||
* This checks for naming conflicts, not for correct symbol references yet.
|
||||
@@ -85,41 +86,8 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
private val keywords = setOf(
|
||||
"void",
|
||||
"on",
|
||||
"in",
|
||||
"to",
|
||||
"if",
|
||||
"downto",
|
||||
"else",
|
||||
"step",
|
||||
"call",
|
||||
"goto",
|
||||
"inline",
|
||||
"struct",
|
||||
"then",
|
||||
"alias",
|
||||
"defer",
|
||||
"const",
|
||||
"sizeof",
|
||||
"as",
|
||||
"return",
|
||||
"break",
|
||||
"continue",
|
||||
"true",
|
||||
"false",
|
||||
"sub",
|
||||
"asmsub",
|
||||
"extsub",
|
||||
"clobbers",
|
||||
"while",
|
||||
"do",
|
||||
"until",
|
||||
"repeat",
|
||||
"unroll",
|
||||
"when"
|
||||
)
|
||||
// the set of reserved keywords in the prog8 language
|
||||
private val keywords: Set<String> by lazy { Prog8Keywords.reservedWords }
|
||||
|
||||
override fun visit(struct: StructDecl) {
|
||||
if(struct.name in BuiltinFunctions)
|
||||
@@ -131,7 +99,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
if(field.type.isArray && field.type.sub==BaseDataType.STRUCT_INSTANCE)
|
||||
errors.err("arrays of struct instances are currently not supported as struct fields at '${field.name}'", struct.position)
|
||||
}
|
||||
|
||||
super.visit(struct)
|
||||
}
|
||||
|
||||
|
||||
@@ -417,10 +417,16 @@ class AstPreprocessor(val program: Program,
|
||||
val tgt = alias.target.targetStatement(program.builtinFunctions)
|
||||
|
||||
if(tgt==null) {
|
||||
// The target may refer to an enum member that hasn't been desugared into consts yet.
|
||||
// Defer the error in that case; a later iteration will resolve it after desugaring.
|
||||
if(refersToNotYetDesugaredEnumMember(alias))
|
||||
return noModifications
|
||||
errors.undefined(alias.target.nameInSource, position = alias.target.position)
|
||||
} else {
|
||||
if(alias.alias == alias.target.nameInSource.first()) {
|
||||
errors.err("alias loop", alias.position)
|
||||
} else if(tgt is Enumeration) {
|
||||
errors.err("cannot alias an enum '${alias.target.nameInSource.joinToString(".")}', alias individual members instead (e.g. ${alias.target.nameInSource.joinToString(".")}::Member)", alias.position)
|
||||
} else if(tgt is Alias) {
|
||||
var chainedAlias = alias
|
||||
var chainedTargetName = alias.target
|
||||
@@ -460,6 +466,23 @@ class AstPreprocessor(val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun refersToNotYetDesugaredEnumMember(alias: Alias): Boolean {
|
||||
val nameInSource = alias.target.nameInSource
|
||||
if(nameInSource.isEmpty())
|
||||
return false
|
||||
val last = nameInSource.last()
|
||||
if("::" !in last)
|
||||
return false
|
||||
val parts = last.split("::", limit = 2)
|
||||
if(parts.size != 2 || parts[0].isEmpty() || parts[1].isEmpty())
|
||||
return false
|
||||
val enumName = parts[0]
|
||||
val memberName = parts[1]
|
||||
val enumPath = nameInSource.dropLast(1) + enumName
|
||||
val enumNode = alias.definingScope.lookup(enumPath) as? Enumeration ?: return false
|
||||
return enumNode.members.any { it.first == memberName }
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<AstModification> {
|
||||
// convert all antlr names to structs (following struct aliases)
|
||||
if(typecast.type.subTypeFromAntlr!=null) {
|
||||
|
||||
@@ -664,6 +664,61 @@ main {
|
||||
}
|
||||
|
||||
|
||||
test("alias to enum member works") {
|
||||
val src = """
|
||||
main {
|
||||
enum Priority { LOW, NORMAL, HIGH, EXTREME }
|
||||
|
||||
alias myNormal = Priority::NORMAL
|
||||
alias chainedNormal = myNormal
|
||||
|
||||
sub start() {
|
||||
ubyte @shared value1 = Priority::NORMAL
|
||||
ubyte @shared value2 = myNormal
|
||||
ubyte @shared value3 = chainedNormal
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)!!
|
||||
val start = result.compilerAst.allBlocks.first { it.name == "main" }.statements.filterIsInstance<prog8.ast.statements.Subroutine>().first { it.name == "start" }
|
||||
val assignments = start.statements.filterIsInstance<Assignment>()
|
||||
val value1 = (assignments[0].value as? NumericLiteral)?.number
|
||||
val value2 = (assignments[1].value as? NumericLiteral)?.number
|
||||
val value3 = (assignments[2].value as? NumericLiteral)?.number
|
||||
value1 shouldBe 1.0
|
||||
value2 shouldBe 1.0
|
||||
value3 shouldBe 1.0
|
||||
}
|
||||
|
||||
test("alias to whole enum gives error") {
|
||||
val src = """
|
||||
main {
|
||||
enum Priority { LOW, NORMAL, HIGH, EXTREME }
|
||||
alias myEnum = Priority
|
||||
|
||||
sub start() {
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "cannot alias an enum"
|
||||
}
|
||||
|
||||
test("alias to nonexistent enum member gives undefined error") {
|
||||
val src = """
|
||||
main {
|
||||
enum Priority { LOW, NORMAL, HIGH, EXTREME }
|
||||
alias myBad = Priority::NONEXISTENT
|
||||
|
||||
sub start() {
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "undefined symbol: Priority::NONEXISTENT"
|
||||
}
|
||||
|
||||
test("double colon in name only allowed in desugared enum members 2") {
|
||||
val src = """
|
||||
main {
|
||||
|
||||
@@ -1718,6 +1718,39 @@ mkword {
|
||||
errors.errors[1] shouldContain "builtin function cannot be redefined"
|
||||
}
|
||||
|
||||
test("block names cannot be builtin functions or keywords") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
}
|
||||
|
||||
private {
|
||||
sub foo() -> uword {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
memory {
|
||||
sub foo() -> uword {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
mkword {
|
||||
sub foo() -> uword {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), optimize=false, src, outputDir, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 3
|
||||
errors.errors[0] shouldContain "name cannot be a keyword"
|
||||
errors.errors[1] shouldContain "builtin function cannot be redefined"
|
||||
errors.errors[2] shouldContain "builtin function cannot be redefined"
|
||||
}
|
||||
|
||||
test("string and array multiplication require integer multiplicand") {
|
||||
val src="""
|
||||
main {
|
||||
|
||||
@@ -248,6 +248,116 @@ class TestExecution6502 : FunSpec({
|
||||
machine.assertMemory(0x02, 1)
|
||||
}
|
||||
|
||||
test("indexed pointer fields in structs") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
struct Node {
|
||||
bool flag
|
||||
^^uword uptr
|
||||
}
|
||||
struct Wrapper {
|
||||
bool z
|
||||
^^Node node
|
||||
}
|
||||
^^Node @shared n = [true, 4000]
|
||||
^^Node @shared inner = [true, 4500]
|
||||
^^Wrapper @shared w = [false, 4000]
|
||||
&ubyte poweroff = $f203
|
||||
&uword slot_low = $0300
|
||||
&uword slot_hi = $0302
|
||||
uword index
|
||||
|
||||
sub start() {
|
||||
n.uptr[3] = 9999
|
||||
n.uptr[1] = 7777
|
||||
n.uptr[9] = 5555
|
||||
slot_low = n.uptr[3]
|
||||
index = 1
|
||||
n.uptr[index] = 1111
|
||||
slot_hi = n.uptr[1]
|
||||
w.node = inner
|
||||
w.node.uptr[2] = 1234
|
||||
poweroff = 1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
val machine = compileResult!!.simulate()
|
||||
machine.assertMemory(0x0300, 9999 and 0xff)
|
||||
machine.assertMemory(0x0301, 9999 shr 8)
|
||||
machine.assertMemory(0x0302, 1111 and 0xff)
|
||||
machine.assertMemory(0x0303, 1111 shr 8)
|
||||
machine.assertMemory(4000+6, 9999 and 0xff)
|
||||
machine.assertMemory(4000+7, 9999 shr 8)
|
||||
machine.assertMemory(4000+2, 1111 and 0xff)
|
||||
machine.assertMemory(4000+3, 1111 shr 8)
|
||||
machine.assertMemory(4000+18, 5555 and 0xff)
|
||||
machine.assertMemory(4000+19, 5555 shr 8)
|
||||
machine.assertMemory(4500+4, 1234 and 0xff)
|
||||
machine.assertMemory(4500+5, 1234 shr 8)
|
||||
}
|
||||
|
||||
test("more struct pointer field operations") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
struct Node {
|
||||
bool flag
|
||||
^^uword uptr
|
||||
}
|
||||
^^Node @shared n = [true, 4000]
|
||||
^^Node @shared m = [true, 5000]
|
||||
^^uword @shared normalptr = 4000
|
||||
&ubyte poweroff = $f203
|
||||
&uword n_after_swap = $0300
|
||||
&uword m_after_swap = $0302
|
||||
&uword n_after_add = $0304
|
||||
&uword lsb_result = $0306
|
||||
&uword msb_result = $0308
|
||||
&uword idxinc_result = $030A
|
||||
|
||||
sub start() {
|
||||
swap(n.uptr, m.uptr)
|
||||
n_after_swap = n.uptr
|
||||
m_after_swap = m.uptr
|
||||
n.uptr += 2
|
||||
n_after_add = n.uptr
|
||||
n.uptr[3] = 100
|
||||
n.uptr[3]++
|
||||
idxinc_result = n.uptr[3]
|
||||
lsb_result = lsb(normalptr)
|
||||
msb_result = msb(normalptr)
|
||||
poweroff = 1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
val machine = compileResult!!.simulate()
|
||||
machine.assertMemory(0x0300, 5000 and 0xff)
|
||||
machine.assertMemory(0x0301, 5000 shr 8)
|
||||
machine.assertMemory(0x0302, 4000 and 0xff)
|
||||
machine.assertMemory(0x0303, 4000 shr 8)
|
||||
machine.assertMemory(0x0304, 5004 and 0xff)
|
||||
machine.assertMemory(0x0305, 5004 shr 8)
|
||||
machine.assertMemory(0x0306, 160)
|
||||
machine.assertMemory(0x0308, 15)
|
||||
machine.assertMemory(0x030A, 101)
|
||||
machine.assertMemory(0x030B, 0)
|
||||
machine.assertMemory(5010, 101)
|
||||
machine.assertMemory(5011, 0)
|
||||
}
|
||||
|
||||
test("serial output") {
|
||||
val src = $$"""
|
||||
%encoding iso
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package prog8.parser
|
||||
|
||||
/**
|
||||
* Provides the complete set of Prog8 reserved words, derived from the
|
||||
* ANTLR grammar vocabulary so it never goes out of sync with the language.
|
||||
*/
|
||||
object Prog8Keywords {
|
||||
val reservedWords: Set<String> by lazy {
|
||||
val words = sortedSetOf<String>()
|
||||
val vocab = Prog8ANTLRParser.VOCABULARY
|
||||
for (i in 0..vocab.maxTokenType) {
|
||||
val literal = vocab.getLiteralName(i) ?: continue
|
||||
// literal is like "'for'" or "'^^'"; keep only word-like ones longer than 1 char
|
||||
if (literal.length >= 2 && literal.startsWith("'") && literal.endsWith("'")) {
|
||||
val word = literal.substring(1, literal.length - 1)
|
||||
if (word.length > 1 && word.matches(Regex("[a-z][a-z_0-9]*"))) {
|
||||
words.add(word)
|
||||
}
|
||||
}
|
||||
}
|
||||
words
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,5 +6,5 @@ org.gradle.configuration-cache=false
|
||||
org.gradle.jvmargs=-Xmx4g
|
||||
kotlin.daemon.jvmargs=-Xmx4g
|
||||
kotlin.code.style=official
|
||||
version=14.0-SNAPSHOT
|
||||
version=14.0-BETA1
|
||||
testMaxHeap=8g
|
||||
Reference in new issue
Block a user