mirror of
https://github.com/irmen/prog8.git
synced 2026-10-24 03:02:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa949165c7 | ||
|
|
d22359b6e7 | ||
|
|
d73709653d | ||
|
|
405926e811 | ||
|
|
36758f41a4 | ||
|
|
7ebc9c79cf | ||
|
|
e0668b55b9 | ||
|
|
76c09da961 | ||
|
|
7e3b8c2c59 | ||
|
|
ecca854c7c | ||
|
|
3b0d7ea960 | ||
|
|
f70fa42eac | ||
|
|
5698de6cf4 | ||
|
|
c5a333a904 | ||
|
|
ff324955dd | ||
|
|
70436f5dca | ||
|
|
31177a2b1b | ||
|
|
4de012fc49 | ||
|
|
ee2888e744 | ||
|
|
efe4df92dc | ||
|
|
723ab54f97 | ||
|
|
d9389afc66 | ||
|
|
e7178ee496 | ||
|
|
d5f35bb3fb | ||
|
|
72f1a779f2 | ||
|
|
3277544295 | ||
|
|
98d2c64d5d | ||
|
|
f68b46fc60 | ||
|
|
d54ab856e7 | ||
|
|
16b24fadea | ||
|
|
b3803cbdf1 | ||
|
|
2ceaa25181 | ||
|
|
513611c5a6 | ||
|
|
7ec4ba40ad | ||
|
|
92374e122b | ||
|
|
94f12732ab | ||
|
|
0904712a00 | ||
|
|
32becdbced | ||
|
|
34aa21f7d9 | ||
|
|
cc81dd7d3e | ||
|
|
335213b55f |
No files matched your search
Generated
+1
-1
@@ -4,6 +4,6 @@
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.0-release-358" />
|
||||
<option name="version" value="1.9.20" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -121,8 +121,8 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
|
||||
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
|
||||
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
|
||||
|
||||
@@ -8,6 +8,7 @@ class CompilationOptions(val output: OutputType,
|
||||
val launcher: CbmPrgLauncherType,
|
||||
val zeropage: ZeropageType,
|
||||
val zpReserved: List<UIntRange>,
|
||||
val zpAllowed: List<UIntRange>,
|
||||
val floats: Boolean,
|
||||
val noSysInit: Boolean,
|
||||
val compTarget: ICompilationTarget,
|
||||
@@ -28,4 +29,8 @@ class CompilationOptions(val output: OutputType,
|
||||
init {
|
||||
compTarget.machine.initializeMemoryAreas(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,13 @@ enum class RegisterOrPair {
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
|
||||
return when(cpu) {
|
||||
CpuRegister.A -> A
|
||||
CpuRegister.X -> X
|
||||
CpuRegister.Y -> Y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun asCpuRegister(): CpuRegister = when(this) {
|
||||
@@ -152,6 +159,7 @@ val ElementToArrayTypes = mapOf(
|
||||
DataType.FLOAT to DataType.ARRAY_F,
|
||||
DataType.BOOL to DataType.ARRAY_BOOL
|
||||
)
|
||||
|
||||
val Cx16VirtualRegisters = arrayOf(
|
||||
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
||||
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
|
||||
@@ -159,6 +167,10 @@ val Cx16VirtualRegisters = arrayOf(
|
||||
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
|
||||
)
|
||||
|
||||
val CpuRegisters = setOf(
|
||||
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
|
||||
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
|
||||
)
|
||||
|
||||
|
||||
enum class OutputType {
|
||||
|
||||
@@ -42,6 +42,13 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
}
|
||||
}
|
||||
|
||||
fun retainAllowed() {
|
||||
synchronized(this) {
|
||||
for(allowed in options.zpAllowed)
|
||||
free.retainAll { it in allowed }
|
||||
}
|
||||
}
|
||||
|
||||
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
|
||||
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
|
||||
fun hasWordAvailable(): Boolean {
|
||||
|
||||
@@ -48,6 +48,7 @@ class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
|
||||
@@ -69,6 +69,7 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
|
||||
@@ -75,6 +75,8 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
|
||||
@@ -47,8 +47,8 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
|
||||
allocateCx16VirtualRegisters()
|
||||
retainAllowed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
|
||||
@@ -503,24 +503,18 @@ class AsmGen6502Internal (
|
||||
return
|
||||
}
|
||||
|
||||
val indexVar = expr.index as? PtIdentifier
|
||||
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.index.position}")
|
||||
|
||||
val indexName = asmVariableName(indexVar)
|
||||
|
||||
if(expr.splitWords) {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" lda $indexName")
|
||||
CpuRegister.X -> out(" ldx $indexName")
|
||||
CpuRegister.Y -> out(" ldy $indexName")
|
||||
}
|
||||
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
|
||||
return
|
||||
}
|
||||
|
||||
when (elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register), false)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
|
||||
out(" asl a")
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
@@ -529,12 +523,13 @@ class AsmGen6502Internal (
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(options.compTarget.memorySize(DataType.FLOAT) == 5) {"invalid float size ${expr.position}"}
|
||||
assignExpressionToRegister(expr.index, RegisterOrPair.A, false)
|
||||
out("""
|
||||
lda $indexName
|
||||
sta P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $indexName"""
|
||||
adc P8ZP_SCRATCH_REG"""
|
||||
)
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
@@ -568,7 +563,7 @@ class AsmGen6502Internal (
|
||||
when(reg) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes)
|
||||
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype in SignedDatatypes, true)
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY,
|
||||
@@ -582,6 +577,34 @@ class AsmGen6502Internal (
|
||||
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
if (value.asConstInteger()==0) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if (isTargetCpu(CpuType.CPU6502))
|
||||
out("lda #0 | sta ${target.asmVarname}")
|
||||
else
|
||||
out("stz ${target.asmVarname}")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val address = target.memory!!.address.asConstInteger()
|
||||
if(address!=null) {
|
||||
if (isTargetCpu(CpuType.CPU6502))
|
||||
out("lda #0 | sta ${address.toHex()}")
|
||||
else
|
||||
out(" stz ${address.toHex()}")
|
||||
return
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
val zero = PtNumber(DataType.UBYTE, 0.0, value.position)
|
||||
zero.parent = value
|
||||
assignExpressionToRegister(zero, target.register!!, false)
|
||||
return
|
||||
}
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
assignRegister(RegisterOrPair.A, target)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.core.Cx16VirtualRegisters
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.core.RegisterOrStatusflag
|
||||
|
||||
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
@@ -11,50 +10,19 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
// order is:
|
||||
// 1) cx16 virtual word registers,
|
||||
// 2) paired CPU registers,
|
||||
// 3) single CPU registers (X last), except A,
|
||||
// 3) single CPU registers (order Y,X,A),
|
||||
// 4) CPU Carry status flag
|
||||
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
|
||||
val args = sub.parameters.withIndex()
|
||||
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
|
||||
val (regsWithoutA, args4) = args3.partition { it.value.first.registerOrPair != RegisterOrPair.A }
|
||||
val (regA, rest) = args4.partition { it.value.first.registerOrPair != null }
|
||||
val (singleRegs, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
regsWithoutA.forEach {
|
||||
if(it.value.first.registerOrPair != RegisterOrPair.X)
|
||||
order += it.index
|
||||
}
|
||||
regsWithoutA.firstOrNull { it.value.first.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
|
||||
singleRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
|
||||
rest.forEach { order += it.index }
|
||||
regA.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
return order
|
||||
}
|
||||
|
||||
fun asmsub6502ArgsHaveRegisterClobberRisk(
|
||||
args: List<PtExpression>,
|
||||
params: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>
|
||||
): Boolean {
|
||||
fun isClobberRisk(expr: PtExpression): Boolean {
|
||||
when (expr) {
|
||||
is PtArrayIndexer -> {
|
||||
return params.any {
|
||||
it.first.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
}
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if (expr.name == "lsb" || expr.name == "msb")
|
||||
return isClobberRisk(expr.args[0])
|
||||
if (expr.name == "mkword")
|
||||
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
|
||||
return !expr.isSimple()
|
||||
}
|
||||
else -> return !expr.isSimple()
|
||||
}
|
||||
}
|
||||
|
||||
return args.size>1 && args.any { isClobberRisk(it) }
|
||||
}
|
||||
@@ -49,7 +49,16 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"peekw" -> funcPeekW(fcall, resultRegister)
|
||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||
"pokew" -> funcPokeW(fcall)
|
||||
"pokemon" -> { /* meme function */ }
|
||||
"pokemon" -> {
|
||||
val memread = PtMemoryByte(fcall.position)
|
||||
memread.add(fcall.args[0])
|
||||
memread.parent = fcall
|
||||
asmgen.assignExpressionToRegister(memread, RegisterOrPair.A, false)
|
||||
asmgen.out(" pha")
|
||||
val memtarget = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.position, memory=memread)
|
||||
asmgen.assignExpressionTo(fcall.args[1], memtarget)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
|
||||
"push" -> asmgen.pushCpuStack(DataType.UBYTE, fcall.args[0])
|
||||
"pushw" -> asmgen.pushCpuStack(DataType.UWORD, fcall.args[0])
|
||||
@@ -110,8 +119,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val var3name = asmgen.asmVariableName(fcall.args[3] as PtIdentifier)
|
||||
val divisionTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[2].position, var2name)
|
||||
val remainderTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, fcall.definingISub(), fcall.args[3].position, var3name)
|
||||
assignAsmGen.assignRegisterByte(remainderTarget, CpuRegister.A, false)
|
||||
assignAsmGen.assignRegisterByte(divisionTarget, CpuRegister.Y, false)
|
||||
assignAsmGen.assignRegisterByte(remainderTarget, CpuRegister.A, false, false)
|
||||
assignAsmGen.assignRegisterByte(divisionTarget, CpuRegister.Y, false, false)
|
||||
}
|
||||
|
||||
private fun funcDivmodW(fcall: PtBuiltinFunctionCall) {
|
||||
@@ -273,11 +282,11 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
when(fcall.args[0].type) {
|
||||
DataType.UBYTE -> {
|
||||
asmgen.out(" ldy #0 | jsr prog8_lib.func_sqrt16_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, false, false)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" jsr floats.func_sqrt_into_FAC1")
|
||||
@@ -686,7 +695,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
assignAsmGen.assignConstantByte(target, 0)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A, false)
|
||||
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,7 +710,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true, true)
|
||||
}
|
||||
|
||||
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
@@ -714,7 +723,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
in SplitWordArrayTypes -> TODO("split word any/all")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes, true)
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
@@ -723,7 +732,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
when (dt) {
|
||||
DataType.BYTE -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_b_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A,false)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A,false, true)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||
@@ -871,7 +880,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
assignAsmGen.assignRegisterByte(targetReg, CpuRegister.A, signed)
|
||||
assignAsmGen.assignRegisterByte(targetReg, CpuRegister.A, signed, true)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
@@ -887,55 +896,59 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
|
||||
private fun funcMin(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
val signed = fcall.type in SignedDatatypes
|
||||
if(fcall.type in ByteDatatypes) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if(signed) asmgen.out(" bmi +") else asmgen.out(" bcc +")
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.A, targetReg)
|
||||
} else if(fcall.type in WordDatatypes) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if(signed) {
|
||||
when (fcall.type) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
if(signed) asmgen.out(" bmi +") else asmgen.out(" bcc +")
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W2+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bpl +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2+1
|
||||
bcc ++
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bcc ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
+""")
|
||||
lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.A, targetReg)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if(signed) {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W2+1
|
||||
bvc +
|
||||
eor #$80
|
||||
+ bpl +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
cmp P8ZP_SCRATCH_W2+1
|
||||
bcc ++
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bcc ++
|
||||
+ lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
jmp ++
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
+""")
|
||||
}
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("min float not supported")
|
||||
}
|
||||
val targetReg = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, signed, fcall.position, fcall.definingISub(), asmgen)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, targetReg)
|
||||
} else {
|
||||
throw AssemblyError("min float not supported")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1111,7 +1124,25 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
when(resultRegister) {
|
||||
if(arg is PtArrayIndexer && resultRegister in setOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
|
||||
// just read the lsb byte out of the word array
|
||||
val arrayVar = if(arg.splitWords) asmgen.asmVariableName(arg.variable)+"_lsb" else asmgen.asmVariableName(arg.variable)
|
||||
when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, arg.type, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar,y")
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, arg.type, CpuRegister.X)
|
||||
asmgen.out(" lda $arrayVar,x")
|
||||
}
|
||||
RegisterOrPair.X -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, arg.type, CpuRegister.Y)
|
||||
asmgen.out(" ldx $arrayVar,y")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else when(resultRegister) {
|
||||
null, RegisterOrPair.A -> {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
|
||||
|
||||
@@ -69,37 +69,59 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
}
|
||||
|
||||
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||
} else {
|
||||
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.parameters)) {
|
||||
registerArgsViaCpuStackEvaluation(call, sub)
|
||||
} else {
|
||||
asmsub6502ArgsEvalOrder(sub).forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
}
|
||||
|
||||
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
|
||||
return when(arg) {
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if (arg.name == "lsb" || arg.name == "msb")
|
||||
return usesOtherRegistersWhileEvaluating(arg.args[0])
|
||||
if (arg.name == "mkword")
|
||||
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
|
||||
return !arg.isSimple()
|
||||
}
|
||||
is PtAddressOf -> false
|
||||
is PtIdentifier -> false
|
||||
is PtMachineRegister -> false
|
||||
is PtMemoryByte -> false
|
||||
is PtNumber -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerArgsViaCpuStackEvaluation(call: PtFunctionCall, callee: PtAsmSub) {
|
||||
// this is called when one or more of the arguments are 'complex' and
|
||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
|
||||
val registersUsed = mutableListOf<RegisterOrStatusflag>();
|
||||
|
||||
if(callee.parameters.isEmpty())
|
||||
return
|
||||
fun usedA() = registersUsed.any {it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY}
|
||||
fun usedX() = registersUsed.any {it.registerOrPair==RegisterOrPair.X || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.XY}
|
||||
fun usedY() = registersUsed.any {it.registerOrPair==RegisterOrPair.Y || it.registerOrPair==RegisterOrPair.AY || it.registerOrPair==RegisterOrPair.XY}
|
||||
|
||||
// use the cpu hardware stack as intermediate storage for the arguments.
|
||||
val argOrder = asmsub6502ArgsEvalOrder(callee)
|
||||
argOrder.reversed().forEach {
|
||||
asmgen.pushCpuStack(callee.parameters[it].second.type, call.args[it])
|
||||
}
|
||||
argOrder.forEach {
|
||||
val param = callee.parameters[it]
|
||||
asmgen.popCpuStack(callee, param.second, param.first)
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||
} else {
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
|
||||
optimalEvalOrder.forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) {
|
||||
if(!registersUsed.any{it.statusflag!=null || it.registerOrPair in CpuRegisters})
|
||||
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
else if(registersUsed.any {it.statusflag!=null}) {
|
||||
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
|
||||
}
|
||||
else {
|
||||
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
|
||||
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
if(usedX()) asmgen.restoreRegisterStack(CpuRegister.X, true)
|
||||
used
|
||||
}
|
||||
} else {
|
||||
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +134,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.type)
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null) {
|
||||
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag {
|
||||
// pass argument via a register parameter
|
||||
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
@@ -156,6 +178,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
}
|
||||
} else throw AssemblyError("can only use Carry as status flag parameter")
|
||||
return RegisterOrStatusflag(null, statusflag)
|
||||
}
|
||||
else {
|
||||
// via register or register pair
|
||||
@@ -188,6 +211,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope)
|
||||
}
|
||||
return RegisterOrStatusflag(register, null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,3 +233,5 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -497,7 +497,15 @@ internal class ProgramAndVarsGen(
|
||||
for ((scopedName, zpvar) in zpVariables) {
|
||||
if (scopedName.startsWith("cx16.r"))
|
||||
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
||||
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
||||
val variable = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||
if(variable.dt in SplitWordArrayTypes) {
|
||||
val lsbAddr = zpvar.address
|
||||
val msbAddr = zpvar.address + (zpvar.size/2).toUInt()
|
||||
asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)")
|
||||
asmgen.out("${scopedName.substringAfterLast('.')}_msb \t= $msbAddr \t; zp ${zpvar.dt} (msbs)")
|
||||
} else {
|
||||
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ internal class AnyExprAsmGen(
|
||||
return assignByteBinExpr(expr, assign)
|
||||
if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||
require(expr.operator in ComparisonOperators)
|
||||
TODO("words operands comparison -> byte")
|
||||
throw AssemblyError("words operands comparison -> byte, should have been handled by assignOptimizedComparisonWords()")
|
||||
}
|
||||
if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
|
||||
require(expr.operator in ComparisonOperators)
|
||||
return assignFloatBinExpr(expr, assign)
|
||||
}
|
||||
TODO("weird expr operand types")
|
||||
throw AssemblyError("weird expr operand types: ${expr.left.type} and {${expr.right.type}")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||
|
||||
@@ -68,14 +68,23 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||
if(value.type != DataType.UBYTE)
|
||||
throw AssemblyError("non-array var indexing requires bytes index")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
|
||||
if(asmgen.isZpVar(value.variable)) {
|
||||
asmgen.out(" lda ($arrayVarName),y")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02) && (value.index as? PtNumber)?.number==0.0) {
|
||||
if (asmgen.isZpVar(value.variable)) {
|
||||
asmgen.out(" lda ($arrayVarName)")
|
||||
} else {
|
||||
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W1)")
|
||||
}
|
||||
} else {
|
||||
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
|
||||
if (asmgen.isZpVar(value.variable)) {
|
||||
asmgen.out(" lda ($arrayVarName),y")
|
||||
} else {
|
||||
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes, false)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,7 +109,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
when (elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue")
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes, false)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | ldy $arrayVarName+$indexValue+1")
|
||||
@@ -118,7 +127,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
in ByteDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVarName,y")
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt in SignedDatatypes, true)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
|
||||
@@ -145,7 +154,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
fun assignViaExprEval(expression: PtExpression) {
|
||||
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, true)
|
||||
}
|
||||
|
||||
val value = assign.source.memory!!
|
||||
@@ -160,7 +169,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
is PtBinaryExpression -> {
|
||||
val addrExpr = value.address as PtBinaryExpression
|
||||
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, addrExpr.operator, false)) {
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, true)
|
||||
} else {
|
||||
assignViaExprEval(value.address)
|
||||
}
|
||||
@@ -214,9 +223,9 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
else -> {
|
||||
// do NOT restore X register before assigning the result values first
|
||||
when (returnValue.first.registerOrPair) {
|
||||
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A, returnValue.second in SignedDatatypes)
|
||||
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X, returnValue.second in SignedDatatypes)
|
||||
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y, returnValue.second in SignedDatatypes)
|
||||
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A, returnValue.second in SignedDatatypes, true)
|
||||
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X, returnValue.second in SignedDatatypes, true)
|
||||
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y, returnValue.second in SignedDatatypes, true)
|
||||
RegisterOrPair.AX -> assignVirtualRegister(assign.target, RegisterOrPair.AX)
|
||||
RegisterOrPair.AY -> assignVirtualRegister(assign.target, RegisterOrPair.AY)
|
||||
RegisterOrPair.XY -> assignVirtualRegister(assign.target, RegisterOrPair.XY)
|
||||
@@ -252,7 +261,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
if(assign.target.register==null) {
|
||||
// still need to assign the result to the target variable/etc.
|
||||
when(returnDt) {
|
||||
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A, returnDt in SignedDatatypes) // function's byte result is in A
|
||||
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A, returnDt in SignedDatatypes, false) // function's byte result is in A
|
||||
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
|
||||
DataType.STR -> {
|
||||
when (assign.target.datatype) {
|
||||
@@ -297,7 +306,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A, signed)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, signed, false)
|
||||
} else {
|
||||
assignExpressionToRegister(value.value, RegisterOrPair.AY, signed)
|
||||
when(value.operator) {
|
||||
@@ -367,7 +376,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
is PtContainmentCheck -> {
|
||||
containmentCheckIntoA(value)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, true)
|
||||
}
|
||||
is PtBinaryExpression -> {
|
||||
if(!attemptAssignOptimizedBinexpr(value, assign)) {
|
||||
@@ -406,7 +415,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda cx16.${register.toString().lowercase()}L")
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
in WordDatatypes -> assignRegisterpairWord(target, register)
|
||||
else -> throw AssemblyError("expected byte or word")
|
||||
@@ -484,7 +493,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
if(target.register==RegisterOrPair.A)
|
||||
asmgen.out(" cmp #0") // fix the status register
|
||||
else
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return true
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@@ -504,7 +513,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" pha")
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
|
||||
asmgen.out(" pla | jsr math.divmod_ub_asm")
|
||||
assignRegisterByte(target, CpuRegister.Y, false)
|
||||
assignRegisterByte(target, CpuRegister.Y, false, true)
|
||||
return true
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
@@ -512,7 +521,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" pha")
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.Y, true)
|
||||
asmgen.out(" pla | jsr math.divmod_b_asm")
|
||||
assignRegisterByte(target, CpuRegister.Y, true)
|
||||
assignRegisterByte(target, CpuRegister.Y, true, true)
|
||||
return true
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@@ -540,7 +549,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" pha")
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.Y, expr.type in SignedDatatypes)
|
||||
asmgen.out(" pla | jsr math.multiply_bytes")
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return true
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
@@ -578,7 +587,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" jsr math.mul_byte_${value}")
|
||||
else
|
||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return true
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
@@ -638,7 +647,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" jsr math.lsr_ubyte_A")
|
||||
else
|
||||
asmgen.out(" jsr math.asl_byte_A")
|
||||
assignRegisterByte(target, CpuRegister.A, signed)
|
||||
assignRegisterByte(target, CpuRegister.A, signed, true)
|
||||
return true
|
||||
} else {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
|
||||
@@ -673,7 +682,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A, signed)
|
||||
assignRegisterByte(target, CpuRegister.A, signed, true)
|
||||
return true
|
||||
}
|
||||
else -> {
|
||||
@@ -682,7 +691,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
} else {
|
||||
asmgen.out(" lda #0")
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A, signed)
|
||||
assignRegisterByte(target, CpuRegister.A, signed, true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -755,7 +764,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" clc | adc $symname")
|
||||
else
|
||||
asmgen.out(" sec | sbc $symname")
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
|
||||
return true
|
||||
}
|
||||
is PtNumber -> {
|
||||
@@ -764,7 +773,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" clc | adc #${right.number.toHex()}")
|
||||
else
|
||||
asmgen.out(" sec | sbc #${right.number.toHex()}")
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
|
||||
return true
|
||||
}
|
||||
else -> {
|
||||
@@ -795,7 +804,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
else
|
||||
asmgen.out(" sec | sbc $arrayvarname,y")
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
|
||||
} else {
|
||||
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", right.type)
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, left.type==DataType.BYTE)
|
||||
@@ -803,7 +812,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
|
||||
else
|
||||
asmgen.out(" sec | sbc P8ZP_SCRATCH_B1")
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes)
|
||||
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -996,7 +1005,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return true
|
||||
}
|
||||
else if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||
@@ -1035,7 +1044,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
else -> return false
|
||||
}
|
||||
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, true)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1405,10 +1414,24 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
|
||||
private fun assignOptimizedComparisonWords(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
val signed = expr.left.type == DataType.WORD || expr.right.type == DataType.WORD
|
||||
|
||||
when(expr.operator) {
|
||||
"==" -> {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
if(expr.left is PtIdentifier) {
|
||||
val varName = asmgen.asmVariableName(expr.left as PtIdentifier)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cmp $varName
|
||||
bne +
|
||||
cpy $varName+1
|
||||
bne +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
} else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
@@ -1417,10 +1440,24 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
}
|
||||
}
|
||||
"!=" -> {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
if(expr.left is PtIdentifier) {
|
||||
val varName = asmgen.asmVariableName(expr.left as PtIdentifier)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cmp $varName
|
||||
bne +
|
||||
cpy $varName+1
|
||||
bne +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
@@ -1429,11 +1466,39 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
}
|
||||
}
|
||||
"<" -> {
|
||||
if(signed) {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
if(expr.right is PtIdentifier) {
|
||||
val varName = asmgen.asmVariableName(expr.right as PtIdentifier)
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.AY)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
cmp $varName
|
||||
tya
|
||||
sbc $varName+1
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bpl ++
|
||||
+ lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
cpy $varName+1
|
||||
bcc +
|
||||
bne ++
|
||||
cmp $varName
|
||||
bcs ++
|
||||
+ lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
} else {
|
||||
if(signed) {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W1+1
|
||||
@@ -1444,10 +1509,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
}
|
||||
else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
}
|
||||
else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bcc +
|
||||
bne ++
|
||||
@@ -1457,12 +1522,40 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
"<=" -> {
|
||||
if(signed) {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
if(expr.left is PtIdentifier) {
|
||||
val varName = asmgen.asmVariableName(expr.left as PtIdentifier)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.AY)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
cmp $varName
|
||||
tya
|
||||
sbc $varName+1
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bmi +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
cpy $varName+1
|
||||
bcc ++
|
||||
bne +
|
||||
cmp $varName
|
||||
bcc ++
|
||||
+ lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
} else {
|
||||
if(signed) {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W1+1
|
||||
@@ -1473,10 +1566,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
}
|
||||
else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
}
|
||||
else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bcc ++
|
||||
bne +
|
||||
@@ -1486,12 +1579,40 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
">" -> {
|
||||
if(signed) {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
if(expr.left is PtIdentifier) {
|
||||
val varName = asmgen.asmVariableName(expr.left as PtIdentifier)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.AY)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
cmp $varName
|
||||
tya
|
||||
sbc $varName+1
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bpl ++
|
||||
+ lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
cpy $varName+1
|
||||
bcc +
|
||||
bne ++
|
||||
cmp $varName
|
||||
bcs ++
|
||||
+ lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
} else {
|
||||
if(signed) {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W1+1
|
||||
@@ -1502,10 +1623,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
}
|
||||
else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
}
|
||||
else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bcc +
|
||||
bne ++
|
||||
@@ -1515,12 +1636,40 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
">=" -> {
|
||||
if(signed) {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
if(expr.right is PtIdentifier) {
|
||||
val varName = asmgen.asmVariableName(expr.right as PtIdentifier)
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.AY)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
cmp $varName
|
||||
tya
|
||||
sbc $varName+1
|
||||
bvc +
|
||||
eor #${'$'}80
|
||||
+ bmi +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
cpy $varName+1
|
||||
bcc ++
|
||||
bne +
|
||||
cmp $varName
|
||||
bcc ++
|
||||
+ lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
} else {
|
||||
if(signed) {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W1+1
|
||||
@@ -1531,10 +1680,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
}
|
||||
else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
}
|
||||
else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bcc ++
|
||||
bne +
|
||||
@@ -1544,12 +1693,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
bne ++
|
||||
+ lda #0
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
|
||||
assignRegisterByte(assign.target, CpuRegister.A, signed)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, signed, true)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1566,7 +1716,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
"^", "xor" -> asmgen.out(" eor $operand")
|
||||
else -> throw AssemblyError("invalid operator")
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
}
|
||||
|
||||
private fun assignLogicalWithSimpleRightOperandWord(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
|
||||
@@ -1605,7 +1755,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, false)
|
||||
return true
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
@@ -1616,13 +1766,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, false)
|
||||
return true
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
|
||||
asmgen.out(" jsr floats.SIGN | and #1 | eor #1")
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, false)
|
||||
return true
|
||||
}
|
||||
else->{
|
||||
@@ -1636,7 +1786,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
asmgen.out(" beq + | lda #1")
|
||||
asmgen.out("+")
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, false)
|
||||
return true
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
@@ -1644,13 +1794,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" beq + | lda #1")
|
||||
asmgen.out("+")
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, false, false)
|
||||
return true
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.FAC1, true)
|
||||
asmgen.out(" jsr floats.SIGN")
|
||||
assignRegisterByte(assign.target, CpuRegister.A, true)
|
||||
assignRegisterByte(assign.target, CpuRegister.A, true, false)
|
||||
return true
|
||||
}
|
||||
else->{
|
||||
@@ -1722,7 +1872,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
else -> throw AssemblyError("can't use Z or N flags as return 'values'")
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
}
|
||||
|
||||
private fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: PtExpression, origTypeCastExpression: PtTypeCast) {
|
||||
@@ -1778,6 +1928,12 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
is PtNumber -> throw AssemblyError("a cast of a literal value should have been const-folded away")
|
||||
is PtArrayIndexer -> {
|
||||
if(targetDt in ByteDatatypes && valueDt in WordDatatypes) {
|
||||
// just assign the lsb from the array value
|
||||
return assignCastViaLsbFunc(value, target)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
@@ -1831,7 +1987,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
RegisterOrPair.XY,
|
||||
in Cx16VirtualRegisters -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
@@ -2761,13 +2917,33 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister, signed: Boolean) {
|
||||
private fun extendToMSBofVirtualReg(cpuRegister: CpuRegister, vreg: String, signed: Boolean) {
|
||||
if(signed) {
|
||||
when(cpuRegister) {
|
||||
CpuRegister.A -> { }
|
||||
CpuRegister.X -> asmgen.out(" txa")
|
||||
CpuRegister.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.out("""
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta $vreg+1""")
|
||||
} else {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $vreg+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $vreg+1")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister, signed: Boolean, extendWord: Boolean) {
|
||||
val assignAsWord = target.datatype in WordDatatypes
|
||||
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out(" st${register.name.lowercase()} ${target.asmVarname}")
|
||||
if(assignAsWord) {
|
||||
if(assignAsWord && extendWord) {
|
||||
if(target.datatype in SignedDatatypes) {
|
||||
if(register!=CpuRegister.A)
|
||||
asmgen.out(" t${register.name.lowercase()}a")
|
||||
@@ -2792,7 +2968,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if(assignAsWord)
|
||||
TODO("assign register byte as word into Array not yet supported")
|
||||
assignRegisterByteToArray(target, register)
|
||||
assignRegisterByteToByteArray(target, register)
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(register) {
|
||||
@@ -2801,6 +2977,9 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
RegisterOrPair.X -> { asmgen.out(" tax") }
|
||||
RegisterOrPair.Y -> { asmgen.out(" tay") }
|
||||
RegisterOrPair.AY -> {
|
||||
require(extendWord) {
|
||||
"no extend"
|
||||
}
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
@@ -2812,6 +2991,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" ldy #0")
|
||||
}
|
||||
RegisterOrPair.AX -> {
|
||||
require(extendWord)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
ldx #0
|
||||
@@ -2823,6 +3003,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" ldx #0")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
require(extendWord)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
tax
|
||||
@@ -2836,8 +3017,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
|
||||
in Cx16VirtualRegisters -> {
|
||||
// only assign a single byte to the virtual register's Lsb
|
||||
asmgen.out(" sta cx16.${target.register.toString().lowercase()}")
|
||||
val reg = "cx16.${target.register.toString().lowercase()}"
|
||||
asmgen.out(" sta $reg")
|
||||
if(extendWord)
|
||||
extendToMSBofVirtualReg(CpuRegister.A, reg, signed)
|
||||
}
|
||||
else -> throw AssemblyError("weird register")
|
||||
}
|
||||
@@ -2846,6 +3029,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
RegisterOrPair.X -> { }
|
||||
RegisterOrPair.Y -> { asmgen.out(" txy") }
|
||||
RegisterOrPair.AY -> {
|
||||
require(extendWord)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
txa
|
||||
@@ -2858,6 +3042,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" txa | ldy #0")
|
||||
}
|
||||
RegisterOrPair.AX -> {
|
||||
require(extendWord)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
txa
|
||||
@@ -2870,6 +3055,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" txa | ldx #0")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
require(extendWord)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
@@ -2882,8 +3068,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
|
||||
in Cx16VirtualRegisters -> {
|
||||
// only assign a single byte to the virtual register's Lsb
|
||||
asmgen.out(" stx cx16.${target.register.toString().lowercase()}")
|
||||
val reg = "cx16.${target.register.toString().lowercase()}"
|
||||
asmgen.out(" stx $reg")
|
||||
if(extendWord)
|
||||
extendToMSBofVirtualReg(CpuRegister.X, reg, signed)
|
||||
}
|
||||
else -> throw AssemblyError("weird register")
|
||||
}
|
||||
@@ -2892,6 +3080,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
RegisterOrPair.X -> { asmgen.out(" tyx") }
|
||||
RegisterOrPair.Y -> { }
|
||||
RegisterOrPair.AY -> {
|
||||
require(extendWord)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
tya
|
||||
@@ -2904,6 +3093,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" tya | ldy #0")
|
||||
}
|
||||
RegisterOrPair.AX -> {
|
||||
require(extendWord)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
tya
|
||||
@@ -2916,6 +3106,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" tya | ldx #0")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
require(extendWord)
|
||||
if(signed)
|
||||
asmgen.out("""
|
||||
tya
|
||||
@@ -2930,8 +3121,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
|
||||
in Cx16VirtualRegisters -> {
|
||||
// only assign a single byte to the virtual register's Lsb
|
||||
asmgen.out(" sty cx16.${target.register.toString().lowercase()}")
|
||||
val reg = "cx16.${target.register.toString().lowercase()}"
|
||||
asmgen.out(" sty $reg")
|
||||
if(extendWord)
|
||||
extendToMSBofVirtualReg(CpuRegister.Y, reg, signed)
|
||||
}
|
||||
else -> throw AssemblyError("weird register")
|
||||
}
|
||||
@@ -2940,7 +3133,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignRegisterByteToArray(target: AsmAssignTarget, register: CpuRegister) {
|
||||
private fun assignRegisterByteToByteArray(target: AsmAssignTarget, register: CpuRegister) {
|
||||
if(target.array!!.splitWords)
|
||||
throw AssemblyError("cannot assign byte to split word array here ${target.position}")
|
||||
|
||||
@@ -3544,7 +3737,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.out(" lda ${address.toHex()}")
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
}
|
||||
TargetStorageKind.REGISTER -> when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda ${address.toHex()}")
|
||||
@@ -3578,7 +3771,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.loadByteFromPointerIntoA(identifier)
|
||||
assignRegisterByte(target, CpuRegister.A, false)
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
asmgen.loadByteFromPointerIntoA(identifier)
|
||||
@@ -3778,10 +3971,16 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(memory.address, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
eor #255""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
asmgen.out("""
|
||||
lda (P8ZP_SCRATCH_W2)
|
||||
eor #255""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
eor #255""")
|
||||
}
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
}
|
||||
|
||||
+298
-222
@@ -205,239 +205,251 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val indexNum = target.array!!.index as? PtNumber
|
||||
val indexVar = target.array.index as? PtIdentifier
|
||||
when {
|
||||
indexNum!=null -> {
|
||||
val targetVarName = if(target.array.splitWords)
|
||||
"${target.asmVarname} + ${indexNum.number.toInt()}"
|
||||
else
|
||||
"${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteVariableWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression!!)
|
||||
}
|
||||
if (indexNum!=null) {
|
||||
val targetVarName = if(target.array.splitWords)
|
||||
"${target.asmVarname} + ${indexNum.number.toInt()}"
|
||||
else
|
||||
"${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationByteVariableWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt())
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationByteVariableWithVariable(targetVarName, target.datatype, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationByteVariableWithValue(targetVarName, target.datatype, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
val block = target.origAstTarget?.definingBlock()
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt(), block)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, value.asmVarname, value.datatype, block)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, regName(value), value.datatype, block)
|
||||
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(targetVarName, target.datatype, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.array!!, block)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression, block)
|
||||
} else {
|
||||
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(targetVarName, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float array")
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(targetVarName, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationFloatWithValue(targetVarName, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationFloatWithValue(targetVarName, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
|
||||
in WordDatatypes -> {
|
||||
val block = target.origAstTarget?.definingBlock()
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationWordWithLiteralval(targetVarName, target.datatype, operator, value.number!!.number.toInt(), block)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, value.asmVarname, value.datatype, block)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationWordWithVariable(targetVarName, target.datatype, operator, regName(value), value.datatype, block)
|
||||
SourceStorageKind.MEMORY -> inplacemodificationWordWithMemread(targetVarName, target.datatype, operator, value.memory!!)
|
||||
SourceStorageKind.ARRAY -> inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.array!!, block)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression, block)
|
||||
} else {
|
||||
inplacemodificationWordWithValue(targetVarName, target.datatype, operator, value.expression!!, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DataType.FLOAT -> {
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(targetVarName, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(targetVarName, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float array")
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(targetVarName, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
inplacemodificationFloatWithValue(targetVarName, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationFloatWithValue(targetVarName, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
indexVar!=null -> {
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
if(value.kind==SourceStorageKind.EXPRESSION
|
||||
&& value.expression is PtTypeCast
|
||||
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
}
|
||||
else {
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
if(value.kind==SourceStorageKind.EXPRESSION
|
||||
&& value.expression is PtTypeCast
|
||||
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda ${target.array.variable.name},y")
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, "#${value.number!!.number.toInt()}", target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, value.asmVarname, target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
|
||||
SourceStorageKind.REGISTER -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, regName(value), target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
|
||||
SourceStorageKind.MEMORY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.memory!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
SourceStorageKind.ARRAY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.array!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression)
|
||||
else
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
asmgen.out(" sta ${target.array.variable.name},y")
|
||||
}
|
||||
|
||||
in WordDatatypes -> {
|
||||
if(value.kind==SourceStorageKind.EXPRESSION
|
||||
&& value.expression is PtTypeCast
|
||||
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(target.array.splitWords) {
|
||||
asmgen.out(" lda ${target.array.variable.name}_lsb,y")
|
||||
asmgen.out(" ldx ${target.array.variable.name}_msb,y")
|
||||
} else {
|
||||
asmgen.out(" lda ${target.array.variable.name},y")
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, "#${value.number!!.number.toInt()}", target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, value.asmVarname, target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
inplacemodificationRegisterAwithVariable(operator, regName(value), target.datatype in SignedDatatypes)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
}
|
||||
SourceStorageKind.MEMORY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.memory!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
SourceStorageKind.ARRAY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.array!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression)
|
||||
else
|
||||
inplacemodificationByteVariableWithValue("P8ZP_SCRATCH_B1", target.datatype, operator, value.expression!!)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
asmgen.out(" sta ${target.array.variable.name},y")
|
||||
asmgen.out(" ldx ${target.array.variable.name}+1,y")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(value.kind==SourceStorageKind.EXPRESSION
|
||||
&& value.expression is PtTypeCast
|
||||
&& tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UWORD, CpuRegister.Y)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(target.array.splitWords) {
|
||||
asmgen.out(" lda ${target.array.variable.name}_lsb,y")
|
||||
asmgen.out(" ldx ${target.array.variable.name}_msb,y")
|
||||
} else {
|
||||
asmgen.out(" lda ${target.array.variable.name},y")
|
||||
asmgen.out(" ldx ${target.array.variable.name}+1,y")
|
||||
}
|
||||
val block = target.origAstTarget?.definingBlock()
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(!inplacemodificationRegisterAXwithLiteralval(operator, number)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithLiteralval("P8ZP_SCRATCH_W1", target.datatype, operator, number, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
if(!inplacemodificationRegisterAXwithVariable(
|
||||
operator,
|
||||
value.asmVarname,
|
||||
value.datatype
|
||||
)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
if(!inplacemodificationRegisterAXwithVariable(
|
||||
operator,
|
||||
regName(value),
|
||||
value.datatype
|
||||
)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.MEMORY -> {
|
||||
val block = target.origAstTarget?.definingBlock()
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(!inplacemodificationRegisterAXwithLiteralval(operator, number)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithMemread("P8ZP_SCRATCH_W1", target.datatype, operator, value.memory!!)
|
||||
inplacemodificationWordWithLiteralval("P8ZP_SCRATCH_W1", target.datatype, operator, number, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
SourceStorageKind.ARRAY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression, block)
|
||||
else
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
if(target.array.splitWords)
|
||||
asmgen.out(" sta ${target.array.variable.name}_lsb,y | txa | sta ${target.array.variable.name}_msb,y")
|
||||
else
|
||||
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
// copy array value into tempvar
|
||||
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
pha ; save array ptr lsb
|
||||
tya
|
||||
pha ; save array ptr msb
|
||||
lda #<$tempvar
|
||||
ldy #>$tempvar
|
||||
jsr floats.copy_float""")
|
||||
|
||||
// calculate on tempvar
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(tempvar, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(tempvar, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(tempvar, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float")
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(tempvar, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
inplacemodificationFloatWithValue(tempvar, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationFloatWithValue(tempvar, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy tempvar back into array
|
||||
asmgen.out("""
|
||||
lda #<$tempvar
|
||||
ldy #>$tempvar
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
pla ; restore array ptr msb
|
||||
tay
|
||||
pla ; restore array ptr lsb
|
||||
jsr floats.copy_float""")
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
if(!inplacemodificationRegisterAXwithVariable(
|
||||
operator,
|
||||
value.asmVarname,
|
||||
value.datatype
|
||||
)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, value.asmVarname, value.datatype, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
|
||||
SourceStorageKind.REGISTER -> {
|
||||
if(!inplacemodificationRegisterAXwithVariable(
|
||||
operator,
|
||||
regName(value),
|
||||
value.datatype
|
||||
)) {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithVariable("P8ZP_SCRATCH_W1", target.datatype, operator, regName(value), value.datatype, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
|
||||
SourceStorageKind.MEMORY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithMemread("P8ZP_SCRATCH_W1", target.datatype, operator, value.memory!!)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
|
||||
SourceStorageKind.ARRAY -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.array!!, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | stx P8ZP_SCRATCH_W1+1")
|
||||
if(value.expression is PtTypeCast)
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression, block)
|
||||
else
|
||||
inplacemodificationWordWithValue("P8ZP_SCRATCH_W1", target.datatype, operator, value.expression!!, block)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | ldx P8ZP_SCRATCH_W1+1")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, true)
|
||||
if(target.array.splitWords)
|
||||
asmgen.out(" sta ${target.array.variable.name}_lsb,y | txa | sta ${target.array.variable.name}_msb,y")
|
||||
else
|
||||
asmgen.out(" sta ${target.array.variable.name},y | txa | sta ${target.array.variable.name}+1,y")
|
||||
}
|
||||
|
||||
DataType.FLOAT -> {
|
||||
// copy array value into tempvar
|
||||
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.FLOAT, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
pha ; save array ptr lsb
|
||||
tya
|
||||
pha ; save array ptr msb
|
||||
lda #<$tempvar
|
||||
ldy #>$tempvar
|
||||
jsr floats.copy_float""")
|
||||
|
||||
// calculate on tempvar
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> inplacemodificationFloatWithLiteralval(tempvar, operator, value.number!!.number)
|
||||
SourceStorageKind.VARIABLE -> inplacemodificationFloatWithVariable(tempvar, operator, value.asmVarname)
|
||||
SourceStorageKind.REGISTER -> inplacemodificationFloatWithVariable(tempvar, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("memread into float")
|
||||
SourceStorageKind.ARRAY -> inplacemodificationFloatWithValue(tempvar, operator, value.array!!)
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator))
|
||||
return
|
||||
inplacemodificationFloatWithValue(tempvar, operator, value.expression)
|
||||
} else {
|
||||
inplacemodificationFloatWithValue(tempvar, operator, value.expression!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy tempvar back into array
|
||||
asmgen.out("""
|
||||
lda #<$tempvar
|
||||
ldy #>$tempvar
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
pla ; restore array ptr msb
|
||||
tay
|
||||
pla ; restore array ptr lsb
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
else -> throw AssemblyError("indexer expression should have been replaced by auto indexer var")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
|
||||
@@ -445,7 +457,6 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
|
||||
private fun inplacemodificationRegisterAXwithVariable(operator: String, variable: String, varDt: DataType): Boolean {
|
||||
// note: we only optimize addition and subtraction, and these are the same for unsigned or signed.
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
return if(varDt in WordDatatypes) {
|
||||
@@ -495,12 +506,44 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
true
|
||||
}
|
||||
}
|
||||
else -> return false // TODO optimize more operators, such as the bitwise logical ones? Might need to know if signed
|
||||
"|" -> {
|
||||
asmgen.out("""
|
||||
ora $variable
|
||||
tay
|
||||
txa
|
||||
ora $variable+1
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
"&" -> {
|
||||
asmgen.out("""
|
||||
and $variable
|
||||
tay
|
||||
txa
|
||||
and $variable+1
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
"^" -> {
|
||||
asmgen.out("""
|
||||
eor $variable
|
||||
tay
|
||||
txa
|
||||
eor $variable+1
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplacemodificationRegisterAXwithLiteralval(operator: String, number: Int): Boolean {
|
||||
// note: we only optimize addition and subtraction, and these are the same for unsigned or signed.
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
return if(number in -128..255) {
|
||||
@@ -544,7 +587,40 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
true
|
||||
}
|
||||
}
|
||||
else -> return false // TODO optimize more operators, such as the bitwise logical ones? Might need to know if signed
|
||||
"|" -> {
|
||||
asmgen.out("""
|
||||
ora #<$number
|
||||
tay
|
||||
txa
|
||||
ora #>$number
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
"&" -> {
|
||||
asmgen.out("""
|
||||
and #<$number
|
||||
tay
|
||||
txa
|
||||
and #>$number
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
"^" -> {
|
||||
asmgen.out("""
|
||||
eor #<$number
|
||||
tay
|
||||
txa
|
||||
eor #>$number
|
||||
tax
|
||||
tya
|
||||
""")
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ class TestCodegen: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
zpReserved = emptyList(),
|
||||
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = target,
|
||||
|
||||
@@ -33,7 +33,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"peekw" -> funcPeekW(call)
|
||||
"poke" -> funcPoke(call)
|
||||
"pokew" -> funcPokeW(call)
|
||||
"pokemon" -> ExpressionCodeResult.EMPTY // easter egg function
|
||||
"pokemon" -> funcPokemon(call)
|
||||
"mkword" -> funcMkword(call)
|
||||
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
|
||||
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
|
||||
@@ -349,13 +349,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val msbTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, msbTr, msbTr.resultReg, -1)
|
||||
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg, reg3 = msbTr.resultReg)
|
||||
if((call.args[0] as? PtNumber)?.number == 0.0) {
|
||||
// msb is 0, use EXT
|
||||
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null)
|
||||
} else {
|
||||
val msbTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, msbTr, msbTr.resultReg, -1)
|
||||
val lsbTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, lsbTr, lsbTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = msbTr.resultReg, reg3 = lsbTr.resultReg), null)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
@@ -547,6 +552,59 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPokemon(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val address = call.args[0]
|
||||
|
||||
fun pokeM(result: MutableList<IRCodeChunkBase>, address: Int, value: PtExpression) {
|
||||
if(codeGen.isZero(value)) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
|
||||
}
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(value)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pokeI(result: MutableList<IRCodeChunkBase>, register: Int, value: PtExpression) {
|
||||
if(codeGen.isZero(value)) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = register)
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(value)
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = register)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if(address is PtNumber) {
|
||||
val resultRegister = codeGen.registers.nextFree()
|
||||
val addressNum = address.number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
|
||||
}
|
||||
pokeM(result, addressNum, call.args[1])
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
} else {
|
||||
val addressTr = exprGen.translateExpression(address)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.nextFree()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
|
||||
}
|
||||
pokeI(result, addressTr.resultReg, call.args[1])
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun funcMemory(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val name = (call.args[0] as PtString).value
|
||||
val code = IRCodeChunk(null, null)
|
||||
|
||||
@@ -188,18 +188,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val memOffset = (arrayIx.index as PtNumber).number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val tmpRegMsb = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb+$memOffset")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
|
||||
}
|
||||
} else {
|
||||
val tr = translateExpression(arrayIx.index)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val tmpRegMsb = codeGen.registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=resultRegister, reg3=tmpRegMsb)
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
|
||||
}
|
||||
}
|
||||
return ExpressionCodeResult(result, vmDt, finalResultReg, -1)
|
||||
|
||||
@@ -491,9 +491,9 @@ class IRCodeGen(
|
||||
val tmpRegLsb = registers.nextFree()
|
||||
val tmpRegMsb = registers.nextFree()
|
||||
val concatReg = registers.nextFree()
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_msb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegLsb, reg3=tmpRegMsb)
|
||||
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, immediate = iterableLength, labelSymbol=iterable.name+"_lsb")
|
||||
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
|
||||
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
|
||||
}
|
||||
result += translateNode(forLoop.statements)
|
||||
|
||||
@@ -45,6 +45,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
val indexedInstructions = chunk1.instructions.withIndex()
|
||||
.map { IndexedValue(it.index, it.value) }
|
||||
val changed = removeNops(chunk1, indexedInstructions)
|
||||
|| replaceConcatZeroMsbWithExt(chunk1, indexedInstructions)
|
||||
|| removeDoubleLoadsAndStores(chunk1, indexedInstructions) // TODO not yet implemented
|
||||
|| removeUselessArithmetic(chunk1, indexedInstructions)
|
||||
|| removeNeedlessCompares(chunk1, indexedInstructions)
|
||||
@@ -63,6 +64,22 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
irprog.linkChunks() // re-link
|
||||
}
|
||||
|
||||
private fun replaceConcatZeroMsbWithExt(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if (ins.opcode == Opcode.CONCAT) {
|
||||
// if the previous instruction loads a zero in the msb, this can be turned into EXT.B instead
|
||||
val prev = indexedInstructions[idx-1].value
|
||||
if(prev.opcode==Opcode.LOAD && prev.immediate==0 && prev.reg1==ins.reg2) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = ins.reg1, reg2 = ins.reg3)
|
||||
chunk.instructions.removeAt(idx-1)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeEmptyChunks(sub: IRSubroutine) {
|
||||
if(sub.chunks.isEmpty())
|
||||
return
|
||||
|
||||
@@ -18,6 +18,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
emptyList(),
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
compTarget = target,
|
||||
|
||||
@@ -19,6 +19,7 @@ class TestVmCodeGen: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
zpReserved = emptyList(),
|
||||
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = target,
|
||||
|
||||
@@ -87,7 +87,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
|
||||
if(expr.operator=="==" && rightconst!=null) {
|
||||
val leftExpr = expr.left as? BinaryExpression
|
||||
if(leftExpr!=null) {
|
||||
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
|
||||
if(leftExpr?.constValue(program) != null) {
|
||||
val leftRightConst = leftExpr.right.constValue(program)
|
||||
if(leftRightConst!=null) {
|
||||
when (leftExpr.operator) {
|
||||
|
||||
@@ -251,7 +251,16 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
|
||||
if(decl.isArray && decl.type==VarDeclType.MEMORY) {
|
||||
val memaddr = decl.value?.constValue(program)
|
||||
if(memaddr!=null && memaddr !== decl.value) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.value = it }, memaddr, decl
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
if(decl.isArray){
|
||||
val arraysize = decl.arraysize
|
||||
if(arraysize==null) {
|
||||
@@ -291,8 +300,11 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
}
|
||||
|
||||
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
|
||||
// convert the initializer range expression from a range or int, to an actual array.
|
||||
|
||||
if(decl.type==VarDeclType.MEMORY)
|
||||
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
|
||||
|
||||
// convert the initializer range expression from a range or int, to an actual array.
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
|
||||
val rangeExpr = decl.value as? RangeExpression
|
||||
|
||||
@@ -114,6 +114,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine.inline && subroutine.statements.size>1) {
|
||||
require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))
|
||||
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visit(subroutine)
|
||||
|
||||
@@ -430,6 +430,27 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val constantValue = whenStmt.condition.constValue(program)?.number
|
||||
if(constantValue!=null) {
|
||||
// when condition is a constant
|
||||
var matchingChoice: WhenChoice? = null
|
||||
loop@ for(choice in whenStmt.choices) {
|
||||
for(value in choice.values ?: emptyList()) {
|
||||
if(value.constValue(program)?.number == constantValue) {
|
||||
matchingChoice = choice
|
||||
break@loop
|
||||
}
|
||||
}
|
||||
}
|
||||
if(matchingChoice==null)
|
||||
matchingChoice = whenStmt.choices.singleOrNull { it.values==null }
|
||||
if(matchingChoice!=null) {
|
||||
// get rid of the whole when-statement and just leave the matching choice
|
||||
return listOf(IAstModification.ReplaceNode(whenStmt, matchingChoice.statements, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -198,6 +198,14 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
sub disable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub enable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
; TODO
|
||||
|
||||
@@ -24,12 +24,24 @@ sub spc() {
|
||||
txt.chrout(' ')
|
||||
}
|
||||
|
||||
asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
const uword COLCRS = 85
|
||||
const uword ROWCRS = 84
|
||||
sub column(ubyte col) {
|
||||
; ---- set the cursor on the given column (starting with 0) on the current line
|
||||
; TODO
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
pokew(COLCRS, col as uword)
|
||||
}
|
||||
|
||||
sub get_column() -> ubyte {
|
||||
return peekw(COLCRS) as ubyte
|
||||
}
|
||||
|
||||
sub row(ubyte rownum) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
@(ROWCRS) = rownum
|
||||
}
|
||||
|
||||
sub get_row() -> ubyte {
|
||||
return @(ROWCRS)
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
|
||||
@@ -372,12 +384,14 @@ sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) {
|
||||
; ---- set cursor at specific position
|
||||
; TODO
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
sub plot(ubyte col, ubyte rownum) {
|
||||
column(col)
|
||||
row(rownum)
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
@(colptr) = get_column()
|
||||
@(rowptr) = get_row()
|
||||
}
|
||||
|
||||
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
|
||||
@@ -734,6 +734,20 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub disable_caseswitch() {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 247
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub enable_caseswitch() {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 247
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-128
|
||||
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
; these work with PETSCII encoding instead.
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -38,6 +40,45 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tax
|
||||
clc
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_row() -> ubyte @X {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tya
|
||||
ldy #$00
|
||||
sta (colptr),y
|
||||
txa
|
||||
sta (rowptr),y
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
; (assumes screen and color matrix are at their default addresses)
|
||||
@@ -238,10 +279,10 @@ _scroll_screen ; scroll only the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; ---- print null terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
@@ -427,7 +468,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
@@ -584,4 +626,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
; bitmap pixel graphics module for the C64
|
||||
; only black/white monochrome 320x200 for now
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
;
|
||||
; Sets character matrix and bitmap screen memory at a higher memory location $5c00-$7fff
|
||||
; so that the program itself can be larger without starting to overwrite the graphics memory.
|
||||
|
||||
@@ -244,7 +247,8 @@ hline_zero2
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp ploty
|
||||
@@ -273,16 +277,17 @@ hline_zero2
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
@@ -295,12 +300,12 @@ hline_zero2
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -737,6 +737,20 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub disable_caseswitch() {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 657
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub enable_caseswitch() {
|
||||
%asm {{
|
||||
lda #0
|
||||
sta 657
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
|
||||
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
; these work with PETSCII encoding instead.
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -39,6 +41,45 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tax
|
||||
clc
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_row() -> ubyte @X {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tya
|
||||
ldy #$00
|
||||
sta (colptr),y
|
||||
txa
|
||||
sta (rowptr),y
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
; (assumes screen and color matrix are at their default addresses)
|
||||
@@ -237,10 +278,10 @@ _scroll_screen ; scroll only the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; ---- print null terminated string, in PESCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
@@ -426,7 +467,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
@@ -583,4 +625,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,16 @@
|
||||
; Commander X16 disk drive I/O routines.
|
||||
; Largely compatible with the default C64 ones, but adds more stuff specific to the X16 as well.
|
||||
|
||||
; NOTE: If you experience weird behavior with these routines and you are using them
|
||||
; in the X16 emulator using HostFs, please try again with an SD-card image instead first.
|
||||
; It is possible that there are still small differences between HostFS and actual CBM DOS in the emulator.
|
||||
;
|
||||
; About the secondary addresses:
|
||||
; for writes (new files or overwrites), you can use 1 (without the mode string) or 2-14 (with the mode string)
|
||||
; for reads (existing files) you can use 0 or 2-14 (mode string is optional)
|
||||
; for modify mode (new files* or existing files), you must use 2-14, and the mode string ,s,m is required
|
||||
|
||||
|
||||
%import textio
|
||||
%import string
|
||||
%import syslib
|
||||
@@ -87,7 +97,7 @@ io_error:
|
||||
sub diskname() -> uword {
|
||||
; returns disk label name or 0 if error
|
||||
cbm.SETNAM(3, "$")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
void cbm.OPEN() ; open 12,8,0,"$=c"
|
||||
if_cs
|
||||
@@ -100,12 +110,12 @@ io_error:
|
||||
; skip up to entry name
|
||||
}
|
||||
|
||||
cx16.r0 = &diskio.list_filename
|
||||
cx16.r0 = &list_filename
|
||||
repeat {
|
||||
@(cx16.r0) = cbm.CHRIN()
|
||||
if @(cx16.r0)=='"' {
|
||||
@(cx16.r0) = ' '
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&list_filename {
|
||||
@(cx16.r0) = 0
|
||||
cx16.r0--
|
||||
}
|
||||
@@ -120,7 +130,7 @@ io_error:
|
||||
cbm.CLOSE(READ_IO_CHANNEL)
|
||||
if status and status & $40 == 0
|
||||
return 0
|
||||
return diskio.list_filename
|
||||
return list_filename
|
||||
}
|
||||
|
||||
; internal variables for the iterative file lister / loader
|
||||
@@ -270,9 +280,11 @@ close_end:
|
||||
|
||||
; ----- iterative file loader functions (uses the read io channel) -----
|
||||
|
||||
sub f_open(uword filenameptr) -> bool {
|
||||
sub f_open(str filenameptr) -> bool {
|
||||
; -- open a file for iterative reading with f_read
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
; Returns true if the file is successfully opened and readable.
|
||||
; No need to check status(), unlike f_open_w() !
|
||||
f_close()
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
@@ -308,13 +320,12 @@ close_end:
|
||||
reset_read_channel()
|
||||
list_blocks = 0 ; we reuse this variable for the total number of bytes read
|
||||
|
||||
; commander X16 supports fast block-read via MACPTR() kernal call
|
||||
uword readsize
|
||||
while num_bytes {
|
||||
readsize = 255
|
||||
if num_bytes<readsize
|
||||
readsize = num_bytes
|
||||
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false)
|
||||
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
|
||||
if_cs
|
||||
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
|
||||
list_blocks += readsize
|
||||
@@ -412,15 +423,35 @@ _end rts
|
||||
|
||||
; ----- iterative file writing functions (uses write io channel) -----
|
||||
|
||||
sub f_open_w(uword filenameptr) -> bool {
|
||||
; -- open a file for iterative writing with f_write
|
||||
f_close_w()
|
||||
sub f_open_w(str filename) -> bool {
|
||||
; -- Open a file for iterative writing with f_write
|
||||
; WARNING: returns true if the open command was received by the device,
|
||||
; but this can still mean the file wasn't successfully opened for writing!
|
||||
; (for example, if it already exists). This is different than f_open()!
|
||||
; To be 100% sure if this call was successful, you have to use status()
|
||||
; and check the drive's status message!
|
||||
return internal_f_open_w(filename, false)
|
||||
}
|
||||
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, 1)
|
||||
void cbm.OPEN() ; open 13,8,1,"filename"
|
||||
if_cc
|
||||
sub f_open_w_seek(str filename) -> bool {
|
||||
; -- Open an existing file for iterative writing with f_write, and seeking with f_seek_w.
|
||||
return internal_f_open_w(filename, true)
|
||||
}
|
||||
|
||||
sub internal_f_open_w(str filename, bool open_for_seeks) -> bool {
|
||||
f_close_w()
|
||||
list_filename = filename
|
||||
str modifier = ",s,?"
|
||||
modifier[3] = 'w'
|
||||
if open_for_seeks
|
||||
modifier[3] = 'm'
|
||||
cx16.r0L = string.append(list_filename, modifier) ; secondary 13 requires a mode suffix to signal we're writing/modifying
|
||||
cbm.SETNAM(cx16.r0L, list_filename)
|
||||
cbm.SETLFS(WRITE_IO_CHANNEL, drivenumber, WRITE_IO_CHANNEL)
|
||||
void cbm.OPEN() ; open 13,8,13,"filename"
|
||||
if_cc {
|
||||
return not cbm.READST()
|
||||
}
|
||||
f_close_w()
|
||||
return false
|
||||
}
|
||||
@@ -429,6 +460,21 @@ _end rts
|
||||
; -- write the given number of bytes to the currently open file
|
||||
if num_bytes!=0 {
|
||||
reset_write_channel()
|
||||
do {
|
||||
cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
|
||||
if_cs
|
||||
goto no_mciout
|
||||
num_bytes -= cx16.r0
|
||||
bufferpointer += cx16.r0
|
||||
if msb(bufferpointer) == $c0
|
||||
bufferpointer = mkword($a0, lsb(bufferpointer)) ; wrap over bank boundary
|
||||
if cbm.READST()!=0
|
||||
return false
|
||||
} until num_bytes==0
|
||||
return not cbm.READST()
|
||||
|
||||
no_mciout:
|
||||
; the device doesn't support MCIOUT, use a normal per-byte write loop
|
||||
repeat num_bytes {
|
||||
cbm.CHROUT(@(bufferpointer))
|
||||
bufferpointer++
|
||||
@@ -516,9 +562,6 @@ io_error:
|
||||
if_cc
|
||||
cx16.r0L = cbm.READST()==0
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
@@ -567,8 +610,6 @@ io_error:
|
||||
+
|
||||
}}
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
return cx16.r1
|
||||
}
|
||||
|
||||
@@ -703,7 +744,7 @@ internal_vload:
|
||||
cx16.r12 = reversebuffer + MAX_PATH_LEN-1
|
||||
@(cx16.r12)=0
|
||||
cbm.SETNAM(3, "$=c")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, diskio.drivenumber, 0)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
void cbm.OPEN() ; open 12,8,0,"$=c"
|
||||
if_cs
|
||||
goto io_error
|
||||
@@ -777,7 +818,7 @@ io_error:
|
||||
sub f_seek(uword pos_hiword, uword pos_loword) {
|
||||
; -- seek in the reading file opened with f_open, to the given 32-bits position
|
||||
ubyte[6] command = ['p',0,0,0,0,0]
|
||||
command[1] = READ_IO_CHANNEL ; f_open uses the read io channel
|
||||
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
|
||||
command[2] = lsb(pos_loword)
|
||||
command[3] = msb(pos_loword)
|
||||
command[4] = lsb(pos_hiword)
|
||||
@@ -791,15 +832,18 @@ io_error:
|
||||
}
|
||||
|
||||
|
||||
; NOTE: f_seek_w() doesn't work reliably right now. I only manage to corrupt the fat32 filesystem on the sdcard with it...
|
||||
; sub f_seek_w(uword pos_hiword, uword pos_loword) {
|
||||
; ; -- seek in the output file opened with f_open_w, to the given 32-bits position
|
||||
; diskio.f_seek.command[1] = WRITE_IO_CHANNEL ; f_open_w uses the write io channel
|
||||
; diskio.f_seek.command[2] = lsb(pos_loword)
|
||||
; diskio.f_seek.command[3] = msb(pos_loword)
|
||||
; diskio.f_seek.command[4] = lsb(pos_hiword)
|
||||
; diskio.f_seek.command[5] = msb(pos_hiword)
|
||||
; goto diskio.f_seek.send_command
|
||||
; }
|
||||
sub f_seek_w(uword pos_hiword, uword pos_loword) {
|
||||
; -- seek in the output file opened with f_open_w_seek, to the given 32-bits position
|
||||
diskio.f_seek.command[1] = WRITE_IO_CHANNEL ; f_open_w uses this secondary address
|
||||
diskio.f_seek.command[2] = lsb(pos_loword)
|
||||
diskio.f_seek.command[3] = msb(pos_loword)
|
||||
diskio.f_seek.command[4] = lsb(pos_hiword)
|
||||
diskio.f_seek.command[5] = msb(pos_hiword)
|
||||
cbm.SETNAM(sizeof(diskio.f_seek.command), &diskio.f_seek.command)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN()
|
||||
cbm.CLOSE(15)
|
||||
reset_write_channel() ; back to the write io channel
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
; Emulator debug interface.
|
||||
; Docs: https://github.com/X16Community/x16-emulator#debug-io-registers
|
||||
; Docs: https://github.com/X16Community/x16-emulator#emulator-io-registers
|
||||
|
||||
emudbg {
|
||||
|
||||
@@ -29,6 +29,7 @@ emudbg {
|
||||
}
|
||||
|
||||
asmsub console_write(str isoString @R0) clobbers(Y) {
|
||||
; note: make sure the text is in Iso encoding.
|
||||
%asm {{
|
||||
ldy #0
|
||||
- lda (cx16.r0),y
|
||||
@@ -41,6 +42,7 @@ emudbg {
|
||||
}
|
||||
|
||||
asmsub console_chrout(ubyte char @A) {
|
||||
; note: make sure the character is in Iso encoding.
|
||||
%asm {{
|
||||
sta p8_EMU_CPUCLK_U
|
||||
}}
|
||||
|
||||
@@ -10,14 +10,17 @@
|
||||
; If you're memory constrained you should probably not use this built-in library,
|
||||
; but make a copy in your project only containing the code for the required resolution.
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
;
|
||||
;
|
||||
; SCREEN MODE LIST:
|
||||
; mode 0 = reset back to default text mode
|
||||
; mode 1 = bitmap 320 x 240 x 256c (8 bpp)
|
||||
; mode 2 = bitmap 640 x 480 x 4c (2 bpp. there's not enough vram for more colors in hires mode.)
|
||||
; mode 3 = bitmap 320 x 240 x 16c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; mode 4 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; higher color depths in highres are not supported due to lack of VRAM
|
||||
; mode 2 = bitmap 640 x 480 x 4c (2 bpp)
|
||||
; mode 3 = bitmap 320 x 240 x 4c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; mode 4 = bitmap 320 x 240 x 16c (not yet implemented: just use 256c, there's enough vram for that)
|
||||
; mode 5 = bitmap 640 x 400 x 16c (not yet implemented)
|
||||
|
||||
gfx2 {
|
||||
|
||||
@@ -40,9 +43,6 @@ gfx2 {
|
||||
cx16.VERA_L1_CONFIG = %00000111
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
width = 320
|
||||
height = 240
|
||||
bpp = 8
|
||||
}
|
||||
2 -> {
|
||||
; highres 4c
|
||||
@@ -52,27 +52,44 @@ gfx2 {
|
||||
cx16.VERA_L1_CONFIG = %00000101
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = %00000001
|
||||
width = 640
|
||||
height = 480
|
||||
bpp = 2
|
||||
}
|
||||
else -> {
|
||||
; back to default text mode
|
||||
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
||||
cbm.CINT()
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
||||
width = 0
|
||||
height = 0
|
||||
bpp = 0
|
||||
mode = 0
|
||||
}
|
||||
}
|
||||
|
||||
active_mode = mode
|
||||
if bpp
|
||||
init_mode(mode)
|
||||
if active_mode
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub init_mode(ubyte mode) {
|
||||
; set the internal configuration variables corresponding to the given screenmode
|
||||
; doesn't manipulate Vera / the actual display mode
|
||||
active_mode = mode
|
||||
when mode {
|
||||
1 -> {
|
||||
width = 320
|
||||
height = 240
|
||||
bpp = 8
|
||||
}
|
||||
2 -> {
|
||||
width = 640
|
||||
height = 480
|
||||
bpp = 2
|
||||
}
|
||||
else -> {
|
||||
width = 0
|
||||
height = 0
|
||||
bpp = 0
|
||||
active_mode = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte color) {
|
||||
position(0, 0)
|
||||
when active_mode {
|
||||
@@ -189,6 +206,24 @@ gfx2 {
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_horizontal_line(uword xx, uword yy, uword length, ubyte color) {
|
||||
; does bounds checking and clipping
|
||||
if msb(yy)&$80!=0 or yy>=height
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
length += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=width
|
||||
return
|
||||
if xx+length>width
|
||||
length = width-xx
|
||||
if length>width
|
||||
return
|
||||
|
||||
horizontal_line(xx, yy, length, color)
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
|
||||
when active_mode {
|
||||
1 -> {
|
||||
@@ -336,6 +371,7 @@ gfx2 {
|
||||
}
|
||||
|
||||
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
@@ -349,36 +385,91 @@ gfx2 {
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 += (yy as word -xx)*2+1
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, color=color.
|
||||
plot(cx16.r14, cx16.r15, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
|
||||
; This version does bounds checks and clipping, but is a lot slower.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, color=color.
|
||||
safe_plot(cx16.r14, cx16.r15, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
@@ -391,12 +482,35 @@ gfx2 {
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
; This version does bounds checks and clipping, but is a lot slower.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,6 +567,15 @@ gfx2 {
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_plot(uword xx, uword yy, ubyte color) {
|
||||
; A plot that does bounds checks to see if the pixel is inside the screen.
|
||||
if msb(xx)&$80!=0 or msb(yy)&$80!=0
|
||||
return
|
||||
if xx >= width or yy >= height
|
||||
return
|
||||
plot(xx, yy, color)
|
||||
}
|
||||
|
||||
sub pget(uword @zp xx, uword yy) -> ubyte {
|
||||
when active_mode {
|
||||
1 -> {
|
||||
@@ -500,11 +623,13 @@ gfx2 {
|
||||
}
|
||||
}
|
||||
|
||||
sub fill(word @zp xx, word @zp yy, ubyte new_color) {
|
||||
sub fill(uword x, uword y, ubyte new_color) {
|
||||
; Non-recursive scanline flood fill.
|
||||
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
|
||||
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
|
||||
const ubyte MAXDEPTH = 48
|
||||
const ubyte MAXDEPTH = 64
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
word[MAXDEPTH] @split @shared stack_xr
|
||||
word[MAXDEPTH] @split @shared stack_y
|
||||
@@ -584,8 +709,11 @@ gfx2 {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
; TODO: if mode==1 (256c) use vera autodecrement instead of pget(), but code bloat not worth it?
|
||||
while xx >= 0 and pget(xx as uword, yy as uword) == cx16.r11L
|
||||
while xx >= 0 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx--
|
||||
}
|
||||
if x1!=xx
|
||||
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
|
||||
else
|
||||
@@ -599,8 +727,11 @@ gfx2 {
|
||||
do {
|
||||
cx16.r9 = xx
|
||||
; TODO: if mode==1 (256c) use vera autoincrement instead of pget(), but code bloat not worth it?
|
||||
while xx <= width-1 and pget(xx as uword, yy as uword) == cx16.r11L
|
||||
while xx <= width-1 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
if cx16.r9!=xx
|
||||
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
|
||||
|
||||
@@ -609,8 +740,11 @@ gfx2 {
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
skip:
|
||||
xx++
|
||||
while xx <= x2 and pget(xx as uword, yy as uword) != cx16.r11L
|
||||
while xx <= x2 {
|
||||
if pget(xx as uword, yy as uword) == cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
left = xx
|
||||
} until xx>x2
|
||||
}
|
||||
|
||||
@@ -7,11 +7,17 @@
|
||||
; Unlike graphics module on the C64, you can use colors() to set new drawing colors for every draw operation.
|
||||
; For other resolutions or other color modes, use the "gfx2" or "monogfx" module instead. (which is Cx16-specific)
|
||||
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
;
|
||||
|
||||
|
||||
graphics {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the normal FB_cursor_position() call but reuses existing r0 and r1
|
||||
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 240
|
||||
|
||||
@@ -69,6 +75,8 @@ graphics {
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
|
||||
;cx16.r0 = xcenter - radius/2
|
||||
;cx16.r1 = ycenter - radius/2
|
||||
;cx16.r2 = radius*2
|
||||
@@ -85,43 +93,47 @@ graphics {
|
||||
while xx>=yy {
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter + yy
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter - yy
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter + xx
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter - xx
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position2()
|
||||
FB_cursor_position2()
|
||||
cx16.FB_set_pixel(stroke_color)
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0 {
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
} else {
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 += (yy as word -xx)*2+1
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
@@ -132,13 +144,14 @@ graphics {
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
; For color bitmap graphics, see the gfx2 library.
|
||||
;
|
||||
; NOTE: a lot of the code here is similar or the same to that in gfx2
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
;
|
||||
|
||||
monogfx {
|
||||
|
||||
@@ -229,6 +232,24 @@ _done
|
||||
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
|
||||
}
|
||||
|
||||
sub safe_horizontal_line(uword xx, uword yy, uword length, bool draw) {
|
||||
; does bounds checking and clipping
|
||||
if msb(yy)&$80!=0 or yy>=height
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
length += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=width
|
||||
return
|
||||
if xx+length>width
|
||||
length = width-xx
|
||||
if length>width
|
||||
return
|
||||
|
||||
horizontal_line(xx, yy, length, draw)
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
|
||||
cx16.r15L = monogfx.plot.maskbits[xx as ubyte & 7] ; bitmask
|
||||
if draw {
|
||||
@@ -385,6 +406,7 @@ _done
|
||||
}
|
||||
|
||||
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
@@ -398,36 +420,91 @@ _done
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 += (yy as word -xx)*2+1
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, draw=draw
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
|
||||
; Does bounds checking and clipping.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, draw=draw
|
||||
safe_plot(cx16.r14, cx16.r15, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
@@ -440,12 +517,35 @@ _done
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += (yy as word)*2+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 += (yy as word -radius)*2+1
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Does bounds checking and clipping.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,6 +597,15 @@ _done
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_plot(uword xx, uword yy, bool draw) {
|
||||
; A plot that does bounds checks to see if the pixel is inside the screen.
|
||||
if msb(xx)&$80!=0 or msb(yy)&$80!=0
|
||||
return
|
||||
if xx >= width or yy >= height
|
||||
return
|
||||
plot(xx, yy, draw)
|
||||
}
|
||||
|
||||
sub pget(uword @zp xx, uword yy) -> ubyte {
|
||||
%asm {{
|
||||
lda xx
|
||||
@@ -525,11 +634,13 @@ _done
|
||||
}}
|
||||
}
|
||||
|
||||
sub fill(word @zp xx, word @zp yy, bool draw) {
|
||||
sub fill(uword x, uword y, bool draw) {
|
||||
; Non-recursive scanline flood fill.
|
||||
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
|
||||
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
|
||||
const ubyte MAXDEPTH = 48
|
||||
const ubyte MAXDEPTH = 64
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
word[MAXDEPTH] @split @shared stack_xr
|
||||
word[MAXDEPTH] @split @shared stack_y
|
||||
@@ -608,8 +719,11 @@ _done
|
||||
while cx16.r12L {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
while xx >= 0 and pget(xx as uword, yy as uword) == cx16.r11L
|
||||
while xx >= 0 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx--
|
||||
}
|
||||
if x1!=xx
|
||||
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
|
||||
else
|
||||
@@ -622,8 +736,11 @@ _done
|
||||
|
||||
do {
|
||||
cx16.r9 = xx
|
||||
while xx <= width-1 and pget(xx as uword, yy as uword) == cx16.r11L
|
||||
while xx <= width-1 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
if cx16.r9!=xx
|
||||
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
|
||||
|
||||
@@ -632,8 +749,11 @@ _done
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
skip:
|
||||
xx++
|
||||
while xx <= x2 and pget(xx as uword, yy as uword) != cx16.r11L
|
||||
while xx <= x2 {
|
||||
if pget(xx as uword, yy as uword) == cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
left = xx
|
||||
} until xx>x2
|
||||
}
|
||||
|
||||
@@ -79,15 +79,13 @@ palette {
|
||||
|
||||
sub set_grayscale() {
|
||||
vera_palette_ptr = $fa00
|
||||
cc=0
|
||||
repeat 16 {
|
||||
cc=0
|
||||
repeat 16 {
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
cc += $11
|
||||
}
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, cc)
|
||||
vera_palette_ptr++
|
||||
cc += $11
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,40 +148,34 @@ palette {
|
||||
|
||||
sub set_c64pepto() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_pepto[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_pepto[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
|
||||
sub set_c64light() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_light[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_light[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
|
||||
sub set_c64dark() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_dark[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
for cc in 0 to 15 {
|
||||
uword ccp = C64_colorpalette_dark[cc]
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
|
||||
vera_palette_ptr++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,6 @@ sprites {
|
||||
}
|
||||
|
||||
sub set_palette_offset(ubyte spritenum, ubyte offset) {
|
||||
sprite_reg = VERA_SPRITEREGS + 7 + spritenum*$0008
|
||||
cx16.vpoke_mask(1, sprite_reg, %11110000, offset>>4)
|
||||
cx16.vpoke_mask(1, VERA_SPRITEREGS + 7 + spritenum*$0008, %11110000, offset>>4)
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ cbm {
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.home or txt.plot
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip, including resetting to the default color palette
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, IRQ, ...)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
@@ -40,7 +40,7 @@ romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device. See also BSAVE
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
@@ -120,6 +120,9 @@ cx16 {
|
||||
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
&uword edkeyvec = $ac03 ; for intercepting BASIN/CHRIN key strokes. See set_basin_handler()
|
||||
&uword edkeybk = $ac05 ; ...the RAM bank of this routine if not in low ram
|
||||
|
||||
|
||||
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
|
||||
&uword r0 = $0002
|
||||
@@ -364,7 +367,6 @@ romsub $fef6 = FB_init() clobbers(A,X,Y)
|
||||
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
|
||||
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte colorcount @X) clobbers(A,X,Y)
|
||||
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
|
||||
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the previous routine, but avoiding having to respecify both x and y every time
|
||||
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
|
||||
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
|
||||
romsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
|
||||
@@ -407,7 +409,7 @@ romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to
|
||||
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
|
||||
romsub $fec3 = kbdbuf_put(ubyte key @A) clobbers(X)
|
||||
romsub $fed2 = keymap(uword identifier @XY, bool read @Pc) -> bool @Pc
|
||||
romsub $ff68 = mouse_config(ubyte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
|
||||
romsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
|
||||
romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
|
||||
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
@@ -450,7 +452,7 @@ asmsub kbdbuf_clear() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub mouse_config2(ubyte shape @A) clobbers (A, X, Y) {
|
||||
asmsub mouse_config2(byte shape @A) clobbers (A, X, Y) {
|
||||
; -- convenience wrapper function that handles the screen resolution for mouse_config() for you
|
||||
%asm {{
|
||||
pha ; save shape
|
||||
@@ -526,16 +528,12 @@ asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
|
||||
; -- get a byte from VERA's video memory
|
||||
; note: inefficient when reading multiple sequential bytes!
|
||||
%asm {{
|
||||
pha
|
||||
lda cx16.VERA_CTRL
|
||||
ora #1
|
||||
sta cx16.VERA_CTRL
|
||||
pla
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
sty cx16.VERA_ADDR_M
|
||||
stx cx16.VERA_ADDR_L
|
||||
lda cx16.VERA_DATA1
|
||||
lda cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -832,6 +830,24 @@ asmsub restore_vera_context() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub set_chrin_keyhandler(ubyte handlerbank @A, uword handler @XY) clobbers(A) {
|
||||
; Install a custom CHRIN (BASIN) key handler. Call this before each line you want to read.
|
||||
; See https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2002%20-%20Editor.md#custom-basin-petscii-code-override-handler
|
||||
%asm {{
|
||||
sei
|
||||
sta cx16.edkeybk
|
||||
lda $00
|
||||
pha
|
||||
stz $00
|
||||
stx cx16.edkeyvec
|
||||
sty cx16.edkeyvec+1
|
||||
pla
|
||||
sta $00
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
sys {
|
||||
@@ -873,6 +889,8 @@ asmsub init_system() {
|
||||
jsr cbm.CHROUT
|
||||
lda #147 ; clear screen
|
||||
jsr cbm.CHROUT
|
||||
lda #8 ; disable charset case switch
|
||||
jsr cbm.CHROUT
|
||||
lda #PROG8_VARSHIGH_RAMBANK
|
||||
sta $00 ; select ram bank
|
||||
lda #0
|
||||
@@ -1250,6 +1268,20 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub disable_caseswitch() {
|
||||
%asm {{
|
||||
lda #8
|
||||
jsr cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub enable_caseswitch() {
|
||||
%asm {{
|
||||
lda #9
|
||||
jsr cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
|
||||
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
; these work with PETSCII encoding instead.
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -42,6 +44,43 @@ asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tax
|
||||
clc
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_row() -> ubyte @X {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tya
|
||||
ldy #$00
|
||||
sta (colptr),y
|
||||
txa
|
||||
sta (rowptr),y
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A, X) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
%asm {{
|
||||
@@ -384,10 +423,10 @@ _nextline
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; ---- print null terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
@@ -572,7 +611,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
@@ -756,4 +796,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -65,10 +65,16 @@ verafx {
|
||||
; unsigned multiplication just passes the values as signed to muls
|
||||
; if you do this yourself in your call to muls, it will save a few instructions.
|
||||
sub mult(uword value1, uword value2) -> uword {
|
||||
; Returns the lower 16 bits of the 32 bits result,
|
||||
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
|
||||
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
|
||||
return muls(value1 as word, value2 as word) as uword
|
||||
}
|
||||
|
||||
asmsub muls(word value1 @R0, word value2 @R1) -> word @AY {
|
||||
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY {
|
||||
; Returns the lower 16 bits of the 32 bits result in AY,
|
||||
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
|
||||
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
|
||||
%asm {{
|
||||
lda #(2 << 1)
|
||||
sta cx16.VERA_CTRL ; $9F25
|
||||
@@ -104,14 +110,13 @@ verafx {
|
||||
sta cx16.VERA_ADDR_H ; so we can read out the result
|
||||
lda cx16.VERA_DATA0
|
||||
ldy cx16.VERA_DATA0
|
||||
ldx cx16.VERA_DATA0 ; store the upper 16 bits of the result in r0
|
||||
stx cx16.r0
|
||||
ldx cx16.VERA_DATA0
|
||||
stx cx16.r0+1
|
||||
stz cx16.VERA_FX_CTRL ; Cache write disable
|
||||
stz cx16.VERA_CTRL ; reset DCSEL
|
||||
rts
|
||||
; we skip the upper 16 bits of the result:
|
||||
; lda cx16.VERA_DATA0
|
||||
; sta $0402
|
||||
; lda cx16.VERA_DATA0
|
||||
; sta $0403
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ io_error:
|
||||
@(cx16.r0) = cbm.CHRIN()
|
||||
if @(cx16.r0)=='"' {
|
||||
@(cx16.r0) = ' '
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&diskio.list_filename {
|
||||
while @(cx16.r0)==' ' and cx16.r0>=&list_filename {
|
||||
@(cx16.r0) = 0
|
||||
cx16.r0--
|
||||
}
|
||||
@@ -476,9 +476,6 @@ io_error:
|
||||
if_cc
|
||||
cx16.r0L = cbm.READST()==0
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
|
||||
return cx16.r0L
|
||||
}
|
||||
|
||||
@@ -506,8 +503,6 @@ io_error:
|
||||
+
|
||||
}}
|
||||
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(1)
|
||||
return cx16.r1
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ multiply_words .proc
|
||||
; - unrolled the two inner loops once
|
||||
;
|
||||
; 16 bit x 16 bit unsigned multiply, 32 bit result
|
||||
; Average cycles:
|
||||
; Average cycles: ~442 ?
|
||||
; 93 bytes
|
||||
|
||||
_multiplicand = P8ZP_SCRATCH_W2 ; 2 bytes
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Internal Math library routines - always included by the compiler
|
||||
; note: some functions you might expect here are builtin functions,
|
||||
; such as abs, sqrt, clamp, min, max for example.
|
||||
|
||||
math {
|
||||
%option no_symbol_prefixing
|
||||
@@ -445,4 +447,41 @@ log2_tab
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub diff(ubyte v1 @A, ubyte v2 @Y) -> ubyte @A {
|
||||
; -- returns the (absolute) difference, or distance, between the two bytes
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_REG
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_REG
|
||||
bcs +
|
||||
eor #255
|
||||
inc a
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub diffw(uword w1 @R0, uword w2 @AY) -> uword @AY {
|
||||
; -- returns the (absolute) difference, or distance, between the two words
|
||||
%asm {{
|
||||
sec
|
||||
sbc cx16.r0L
|
||||
sta cx16.r0L
|
||||
tya
|
||||
sbc cx16.r0H
|
||||
sta cx16.r0H
|
||||
bcs +
|
||||
eor #255
|
||||
sta cx16.r0H
|
||||
lda cx16.r0L
|
||||
eor #255
|
||||
inc a
|
||||
sta cx16.r0L
|
||||
bne +
|
||||
inc cx16.r0H
|
||||
+ lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -297,6 +297,14 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
sub disable_caseswitch() {
|
||||
; PET doesn't have a key to swap case, so no-op
|
||||
}
|
||||
|
||||
sub enable_caseswitch() {
|
||||
; PET doesn't have a key to swap case, so no-op
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore PET
|
||||
; All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
; these work with PETSCII encoding instead.
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
@@ -28,6 +30,45 @@ sub spc() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
asmsub get_column() -> ubyte @Y {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub row(ubyte rownum @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given row (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tax
|
||||
clc
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_row() -> ubyte @X {
|
||||
%asm {{
|
||||
sec
|
||||
jmp cbm.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_cursor(uword colptr, uword rowptr) {
|
||||
%asm {{
|
||||
sec
|
||||
jsr cbm.PLOT
|
||||
tya
|
||||
ldy #$00
|
||||
sta (colptr),y
|
||||
txa
|
||||
sta (rowptr),y
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character. color is ignored on PET
|
||||
%asm {{
|
||||
@@ -139,10 +180,10 @@ asmsub scroll_down () clobbers(A,X) {
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse.
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; ---- print null terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
@@ -328,7 +369,8 @@ asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
@@ -453,4 +495,11 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitkey() {
|
||||
%asm {{
|
||||
- jsr cbm.GETIN
|
||||
beq -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -146,11 +146,11 @@ _startloop dey
|
||||
iny
|
||||
bne -
|
||||
_notfound lda #0
|
||||
clc
|
||||
clc
|
||||
rts
|
||||
_found tya
|
||||
sec
|
||||
rts
|
||||
_found tya
|
||||
sec
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,31 @@ _found tya
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub append(uword target @R0, uword suffix @R1) clobbers(Y) -> ubyte @A {
|
||||
; Append the suffix string to the target. (make sure the buffer is large enough!)
|
||||
; Returns the length of the resulting string.
|
||||
%asm {{
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr length
|
||||
sty P8ZP_SCRATCH_B1
|
||||
tya
|
||||
clc
|
||||
adc cx16.r0
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda cx16.r0+1
|
||||
adc #0
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jsr prog8_lib.strcpy
|
||||
tya
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting.
|
||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
; Internal Math library routines - always included by the compiler
|
||||
; note: some functions you might expect here are builtin functions,
|
||||
; such as abs, sqrt, clamp, min, max for example.
|
||||
|
||||
math {
|
||||
|
||||
@@ -277,4 +279,17 @@ math {
|
||||
returnr.w r0
|
||||
}}
|
||||
}
|
||||
|
||||
sub diff(ubyte b1, ubyte b2) -> ubyte {
|
||||
if b1>b2
|
||||
return b1-b2
|
||||
return b2-b1
|
||||
}
|
||||
|
||||
sub diffw(uword w1, uword w2) -> uword {
|
||||
if w1>w2
|
||||
return w1-w2
|
||||
return w2-w1
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
; Monochrome Bitmap pixel graphics routines for the Virtual Machine
|
||||
; Using the full-screen 640x480 and 320x240 screen modes, but just black/white.
|
||||
;
|
||||
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
|
||||
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
|
||||
|
||||
|
||||
monogfx {
|
||||
|
||||
%option no_symbol_prefixing
|
||||
|
||||
; read-only control variables:
|
||||
uword width = 0
|
||||
uword height = 0
|
||||
bool dont_stipple_flag = true ; set to false to enable stippling mode
|
||||
|
||||
sub lores() {
|
||||
; enable 320*240 bitmap mode
|
||||
sys.gfx_enable(0)
|
||||
width = 320
|
||||
height = 240
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub hires() {
|
||||
; enable 640*480 bitmap mode
|
||||
sys.gfx_enable(1)
|
||||
width = 640
|
||||
height = 480
|
||||
clear_screen(0)
|
||||
}
|
||||
|
||||
sub textmode() {
|
||||
; back to normal text mode
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte color) {
|
||||
stipple(false)
|
||||
if color
|
||||
color=255
|
||||
sys.gfx_clear(color)
|
||||
}
|
||||
|
||||
sub stipple(bool enable) {
|
||||
dont_stipple_flag = not enable
|
||||
}
|
||||
|
||||
sub rect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
|
||||
if rwidth==0 or rheight==0
|
||||
return
|
||||
horizontal_line(xx, yy, rwidth, draw)
|
||||
if rheight==1
|
||||
return
|
||||
horizontal_line(xx, yy+rheight-1, rwidth, draw)
|
||||
vertical_line(xx, yy+1, rheight-2, draw)
|
||||
if rwidth==1
|
||||
return
|
||||
vertical_line(xx+rwidth-1, yy+1, rheight-2, draw)
|
||||
}
|
||||
|
||||
sub fillrect(uword xx, uword yy, uword rwidth, uword rheight, bool draw) {
|
||||
; Draw a filled rectangle of the given size and color.
|
||||
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
||||
if rwidth==0
|
||||
return
|
||||
repeat rheight {
|
||||
horizontal_line(xx, yy, rwidth, draw)
|
||||
yy++
|
||||
}
|
||||
}
|
||||
|
||||
sub horizontal_line(uword xx, uword yy, uword length, bool draw) {
|
||||
ubyte color = 0
|
||||
if draw
|
||||
color = 255
|
||||
uword xpos
|
||||
for xpos in xx to xx+length-1
|
||||
plot(xpos, yy, color)
|
||||
}
|
||||
|
||||
sub safe_horizontal_line(uword xx, uword yy, uword length, bool draw) {
|
||||
; does bounds checking and clipping
|
||||
if msb(yy)&$80!=0 or yy>=height
|
||||
return
|
||||
if msb(xx)&$80!=0 {
|
||||
length += xx
|
||||
xx = 0
|
||||
}
|
||||
if xx>=width
|
||||
return
|
||||
if xx+length>width
|
||||
length = width-xx
|
||||
if length>width
|
||||
return
|
||||
|
||||
horizontal_line(xx, yy, length, draw)
|
||||
}
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, bool draw) {
|
||||
ubyte color = 0
|
||||
if draw
|
||||
color = 255
|
||||
uword ypos
|
||||
for ypos in yy to yy+lheight-1
|
||||
plot(xx, ypos, color)
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, bool draw) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
cx16.r0 = x1
|
||||
x1 = x2
|
||||
x2 = cx16.r0
|
||||
cx16.r0 = y1
|
||||
y1 = y2
|
||||
y2 = cx16.r0
|
||||
}
|
||||
word @zp dx = (x2 as word)-x1
|
||||
word @zp dy = (y2 as word)-y1
|
||||
|
||||
if dx==0 {
|
||||
vertical_line(x1, y1, abs(dy) as uword +1, draw)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx) as uword +1, draw)
|
||||
return
|
||||
}
|
||||
|
||||
word @zp d = 0
|
||||
cx16.r1L = true ; 'positive_ix'
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
cx16.r1L = false
|
||||
}
|
||||
word @zp dx2 = dx*2
|
||||
word @zp dy2 = dy*2
|
||||
cx16.r14 = x1 ; internal plot X
|
||||
|
||||
if dx >= dy {
|
||||
if cx16.r1L {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14++
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14--
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if cx16.r1L {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14++
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, draw)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14--
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, draw=draw
|
||||
plot(cx16.r14, cx16.r15, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, bool draw) {
|
||||
; Does bounds checking and clipping.
|
||||
; Midpoint algorithm.
|
||||
if radius==0
|
||||
return
|
||||
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-xx
|
||||
; R14 = internal plot X
|
||||
; R15 = internal plot Y
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter + yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + xx
|
||||
cx16.r15 = ycenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter + xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
cx16.r14 = xcenter + yy
|
||||
cx16.r15 = ycenter - xx
|
||||
plotq()
|
||||
cx16.r14 = xcenter - yy
|
||||
plotq()
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
xx--
|
||||
decisionOver2 -= xx*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
|
||||
sub plotq() {
|
||||
; cx16.r14 = x, cx16.r15 = y, draw=draw
|
||||
safe_plot(cx16.r14, cx16.r15, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
|
||||
while radius>=yy {
|
||||
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
decisionOver2 -= radius*$0002
|
||||
}
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
|
||||
if draw {
|
||||
if dont_stipple_flag
|
||||
sys.gfx_plot(xx, yy, 255)
|
||||
else {
|
||||
if (xx ^ yy)&1
|
||||
sys.gfx_plot(xx, yy, 255)
|
||||
else
|
||||
sys.gfx_plot(xx, yy, 0)
|
||||
}
|
||||
}
|
||||
else
|
||||
sys.gfx_plot(xx, yy, 0)
|
||||
}
|
||||
|
||||
sub safe_plot(uword xx, uword yy, bool draw) {
|
||||
; A plot that does bounds checks to see if the pixel is inside the screen.
|
||||
if msb(xx)&$80!=0 or msb(yy)&$80!=0
|
||||
return
|
||||
if xx >= width or yy >= height
|
||||
return
|
||||
plot(xx, yy, draw)
|
||||
}
|
||||
|
||||
sub pget(uword @zp xx, uword yy) -> ubyte {
|
||||
return sys.gfx_getpixel(xx, yy)
|
||||
}
|
||||
|
||||
sub fill(uword x, uword y, bool draw) {
|
||||
; Non-recursive scanline flood fill.
|
||||
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
|
||||
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
|
||||
const ubyte MAXDEPTH = 64
|
||||
word @zp xx = x as word
|
||||
word @zp yy = y as word
|
||||
word[MAXDEPTH] @split @shared stack_xl
|
||||
word[MAXDEPTH] @split @shared stack_xr
|
||||
word[MAXDEPTH] @split @shared stack_y
|
||||
byte[MAXDEPTH] @shared stack_dy
|
||||
cx16.r12L = 0 ; stack pointer
|
||||
word x1
|
||||
word x2
|
||||
byte dy
|
||||
cx16.r10L = draw as ubyte
|
||||
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
|
||||
if cx16.r12L==MAXDEPTH
|
||||
return
|
||||
cx16.r0s = sy+sdy
|
||||
if cx16.r0s>=0 and cx16.r0s<=height-1 {
|
||||
stack_xl[cx16.r12L] = sxl
|
||||
stack_xr[cx16.r12L] = sxr
|
||||
stack_y[cx16.r12L] = sy
|
||||
stack_dy[cx16.r12L] = sdy
|
||||
cx16.r12L++
|
||||
}
|
||||
}
|
||||
sub pop_stack() {
|
||||
cx16.r12L--
|
||||
x1 = stack_xl[cx16.r12L]
|
||||
x2 = stack_xr[cx16.r12L]
|
||||
yy = stack_y[cx16.r12L]
|
||||
dy = stack_dy[cx16.r12L]
|
||||
yy+=dy
|
||||
}
|
||||
cx16.r11L = pget(xx as uword, yy as uword) ; old_color
|
||||
if cx16.r11L == cx16.r10L
|
||||
return
|
||||
if xx<0 or xx>width-1 or yy<0 or yy>height-1
|
||||
return
|
||||
push_stack(xx, xx, yy, 1)
|
||||
push_stack(xx, xx, yy + 1, -1)
|
||||
word left = 0
|
||||
while cx16.r12L {
|
||||
pop_stack()
|
||||
xx = x1
|
||||
while xx >= 0 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx--
|
||||
}
|
||||
if x1!=xx
|
||||
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
|
||||
else
|
||||
goto skip
|
||||
|
||||
left = xx + 1
|
||||
if left < x1
|
||||
push_stack(left, x1 - 1, yy, -dy)
|
||||
xx = x1 + 1
|
||||
|
||||
do {
|
||||
cx16.r9 = xx
|
||||
while xx <= width-1 {
|
||||
if pget(xx as uword, yy as uword) != cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
if cx16.r9!=xx
|
||||
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L)
|
||||
|
||||
push_stack(left, xx - 1, yy, dy)
|
||||
if xx > x2 + 1
|
||||
push_stack(x2 + 1, xx - 1, yy, -dy)
|
||||
skip:
|
||||
xx++
|
||||
while xx <= x2 {
|
||||
if pget(xx as uword, yy as uword) == cx16.r11L
|
||||
break
|
||||
xx++
|
||||
}
|
||||
left = xx
|
||||
} until xx>x2
|
||||
}
|
||||
}
|
||||
|
||||
sub text_charset(ubyte charset) {
|
||||
; -- select the text charset to use with the text() routine
|
||||
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
|
||||
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
|
||||
; TODO vm has no bitmap charset
|
||||
}
|
||||
|
||||
sub text(uword @zp xx, uword yy, bool draw, uword sctextptr) {
|
||||
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
||||
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||
; TODO vm has no bitmap charset
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,13 @@ string {
|
||||
}
|
||||
}
|
||||
|
||||
sub append(str target, str suffix) -> ubyte {
|
||||
; Append the suffix string to the target. (make sure the buffer is large enough!)
|
||||
; Returns the length of the resulting string.
|
||||
cx16.r0L = length(target)
|
||||
return copy(suffix, target+cx16.r0L) + cx16.r0L
|
||||
}
|
||||
|
||||
sub compare(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting.
|
||||
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||
|
||||
@@ -79,6 +79,13 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
sub disable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub enable_caseswitch() {
|
||||
; no-op
|
||||
}
|
||||
|
||||
sub gfx_enable(ubyte mode) {
|
||||
%ir {{
|
||||
|
||||
@@ -5,11 +5,11 @@ package prog8.buildversion
|
||||
*/
|
||||
const val MAVEN_GROUP = "prog8"
|
||||
const val MAVEN_NAME = "compiler"
|
||||
const val VERSION = "9.5.1"
|
||||
const val GIT_REVISION = 4157
|
||||
const val GIT_SHA = "3dc5a0e7f8e5a94fdb4c13ff9199a4bf912268f2"
|
||||
const val GIT_DATE = "2023-10-18T21:59:37Z"
|
||||
const val VERSION = "9.6-SNAPSHOT"
|
||||
const val GIT_REVISION = 4159
|
||||
const val GIT_SHA = "335213b55f971452a05c904216cb0fd72f5518a1"
|
||||
const val GIT_DATE = "2023-10-21T00:16:58Z"
|
||||
const val GIT_BRANCH = "master"
|
||||
const val BUILD_DATE = "2023-10-19T19:55:55Z"
|
||||
const val BUILD_UNIX_TIME = 1697745355291L
|
||||
const val BUILD_DATE = "2023-10-21T20:22:14Z"
|
||||
const val BUILD_UNIX_TIME = 1697919734197L
|
||||
const val DIRTY = 1
|
||||
@@ -107,6 +107,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
|
||||
if (args.writeAssembly) {
|
||||
|
||||
// re-initialize memory areas with final compilationOptions
|
||||
compilationOptions.compTarget.machine.initializeMemoryAreas(compilationOptions)
|
||||
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
|
||||
args.errors.report()
|
||||
@@ -301,6 +302,14 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
.map { it[0].int!!..it[1].int!! }
|
||||
.toList()
|
||||
|
||||
val zpAllowed = toplevelModule.statements
|
||||
.asSequence()
|
||||
.filter { it is Directive && it.directive == "%zpallowed" }
|
||||
.map { (it as Directive).args }
|
||||
.filter { it.size==2 && it[0].int!=null && it[1].int!=null }
|
||||
.map { it[0].int!!..it[1].int!! }
|
||||
.toList()
|
||||
|
||||
val outputType = if (outputTypeStr == null) {
|
||||
if(compTarget is AtariTarget)
|
||||
OutputType.XEX
|
||||
@@ -330,7 +339,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
|
||||
return CompilationOptions(
|
||||
outputType, launcherType,
|
||||
zpType, zpReserved, floatsEnabled, noSysInit,
|
||||
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit,
|
||||
compTarget, 0u
|
||||
)
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class ModuleImporter(private val program: Program,
|
||||
|
||||
private fun removeDirectivesFromImportedModule(importedModule: Module) {
|
||||
// Most global directives don't apply for imported modules, so remove them
|
||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address")
|
||||
var directives = importedModule.statements.filterIsInstance<Directive>()
|
||||
importedModule.statements.removeAll(directives.toSet())
|
||||
directives = directives.filter{ it.directive !in moduleLevelDirectives }
|
||||
|
||||
@@ -773,7 +773,7 @@ internal class AstChecker(private val program: Program,
|
||||
directive.args[0].name != "full")
|
||||
err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, dontuse, or full")
|
||||
}
|
||||
"%zpreserved" -> {
|
||||
"%zpreserved", "%zpallowed" -> {
|
||||
if(directive.parent !is Module)
|
||||
err("this directive may only occur at module level")
|
||||
if(directive.args.size!=2 || directive.args[0].int==null || directive.args[1].int==null)
|
||||
@@ -1348,6 +1348,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(whenStmt.choices.isEmpty())
|
||||
errors.err("empty when statement", whenStmt.position)
|
||||
|
||||
if(whenStmt.condition.constValue(program)!=null)
|
||||
errors.warn("when-value is a constant and will always result in the same choice", whenStmt.condition.position)
|
||||
|
||||
super.visit(whenStmt)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.getTempVar
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.ContainmentCheck
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
@@ -155,35 +157,4 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(options.compTarget.name!=VMTarget.NAME) { // don't apply this optimization/check for Vm target
|
||||
val index = arrayIndexedExpression.indexer.indexExpr
|
||||
if (index !is NumericLiteral && index !is IdentifierReference) {
|
||||
// replace complex indexing expression with a temp variable to hold the computed index first
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val (tempVarName, _) = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
|
||||
val target = AssignTarget(IdentifierReference(tempVarName, expr.indexer.position), null, null, expr.indexer.position)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.ASMGEN, expr.indexer.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
|
||||
modifications.add(
|
||||
IAstModification.ReplaceNode(
|
||||
expr.indexer.indexExpr,
|
||||
target.identifier!!.copy(),
|
||||
expr.indexer
|
||||
)
|
||||
)
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ internal class StatementReorderer(
|
||||
// - sorts the choices in when statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
|
||||
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address", "%option")
|
||||
|
||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
@@ -52,10 +52,6 @@ internal class StatementReorderer(
|
||||
// This allows you to restart the program and have the same starting values of the variables
|
||||
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
|
||||
decl.value = null
|
||||
if(decl.name.startsWith("tempvar_") && decl.definingScope.name=="prog8_lib") {
|
||||
// no need to zero out the special internal temporary variables.
|
||||
return noModifications
|
||||
}
|
||||
if(decl.findInitializer(program)!=null)
|
||||
return noModifications // an initializer assignment for a vardecl is already here
|
||||
val nextFor = decl.nextSibling() as? ForLoop
|
||||
|
||||
@@ -9,6 +9,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.sign
|
||||
|
||||
|
||||
class TypecastsAdder(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : AstWalker() {
|
||||
@@ -245,96 +246,54 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
|
||||
// see if a typecast is needed to convert the arguments into the required parameter's type
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
when(val sub = call.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
sub.parameters.zip(call.args).forEach { (param, arg) ->
|
||||
val argItype = arg.inferType(program)
|
||||
if(argItype.isKnown) {
|
||||
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||
val requiredType = param.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
// don't need a cast for pass-by-reference types that are assigned to UWORD
|
||||
if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
|
||||
addTypecastOrCastedValueModification(modifications, arg, requiredType, call as Node)
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// We allow STR/ARRAY values in place of UWORD parameters.
|
||||
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
|
||||
val identifier = arg as? IdentifierReference
|
||||
if(identifier?.isSubroutineParameter(program)==false) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
AddressOf(identifier, null, arg.position),
|
||||
call as Node)
|
||||
}
|
||||
} else if(arg is NumericLiteral) {
|
||||
val cast = arg.cast(requiredType)
|
||||
if(cast.isValid)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
arg,
|
||||
cast.valueOrZero(),
|
||||
call as Node)
|
||||
} else if(requiredType==DataType.BOOL && argtype!=DataType.BOOL) {
|
||||
// cast to bool
|
||||
addTypecastOrCastedValueModification(modifications, arg, requiredType, call as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the argument is an identifier reference and the param is UWORD, add the missing &.
|
||||
if(arg is IdentifierReference && DataType.UWORD == param.type) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
arg,
|
||||
AddressOf(arg, null, arg.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(sub.name)
|
||||
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
if (argItype.isKnown) {
|
||||
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||
for (possibleType in pair.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
addTypecastOrCastedValueModification(modifications, pair.second, possibleType, call as Node)
|
||||
break
|
||||
}
|
||||
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
|
||||
// We allow STR/ARRAY values in place of UWORD parameters.
|
||||
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
|
||||
val identifier = pair.second as? IdentifierReference
|
||||
if(identifier?.isSubroutineParameter(program)==false) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
AddressOf(identifier, null, pair.second.position),
|
||||
call as Node)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the argument is an identifier reference and the param is UWORD, add the missing &.
|
||||
if(pair.second is IdentifierReference && DataType.UWORD in pair.first.possibleDatatypes) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
AddressOf(pair.second as IdentifierReference, null, pair.second.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> { }
|
||||
val sub = call.target.targetStatement(program)
|
||||
val params = when(sub) {
|
||||
is BuiltinFunctionPlaceholder -> BuiltinFunctions.getValue(sub.name).parameters
|
||||
is Subroutine -> sub.parameters.map { FParam(it.name, listOf(it.type).toTypedArray()) }
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
params.zip(call.args).forEach {
|
||||
val targetDt = it.first.possibleDatatypes.first()
|
||||
val argIdt = it.second.inferType(program)
|
||||
if (argIdt.isKnown) {
|
||||
val argDt = argIdt.getOr(DataType.UNDEFINED)
|
||||
if (argDt !in it.first.possibleDatatypes) {
|
||||
val identifier = it.second as? IdentifierReference
|
||||
val number = it.second as? NumericLiteral
|
||||
if(number!=null) {
|
||||
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
|
||||
} else if(identifier!=null && targetDt==DataType.UWORD && argDt in PassByReferenceDatatypes) {
|
||||
if(!identifier.isSubroutineParameter(program)) {
|
||||
// We allow STR/ARRAY values for UWORD parameters.
|
||||
// If it's an array (not STR), take the address.
|
||||
if(argDt != DataType.STR) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
AddressOf(identifier, null, it.second.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if(targetDt==DataType.BOOL) {
|
||||
addTypecastOrCastedValueModification(modifications, it.second, DataType.BOOL, call as Node)
|
||||
} else if(argDt isAssignableTo targetDt) {
|
||||
if(argDt!=DataType.STR || targetDt!=DataType.UWORD)
|
||||
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val identifier = it.second as? IdentifierReference
|
||||
if(identifier!=null && targetDt==DataType.UWORD) {
|
||||
// take the address of the identifier
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
AddressOf(identifier, null, it.second.position),
|
||||
call as Node
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return modifications
|
||||
}
|
||||
|
||||
@@ -448,7 +407,11 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
if(expressionToCast is NumericLiteral && expressionToCast.type!=DataType.FLOAT) { // refuse to automatically truncate floats
|
||||
val castedValue = expressionToCast.cast(requiredType)
|
||||
if (castedValue.isValid) {
|
||||
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
|
||||
val signOriginal = sign(expressionToCast.number)
|
||||
val signCasted = sign(castedValue.valueOrZero().number)
|
||||
if(signOriginal==signCasted) {
|
||||
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,24 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
|
||||
errors.err("memory block '${slab.name}' already exists with a different size and/or alignment at ${other.position}", slab.position)
|
||||
}
|
||||
}
|
||||
|
||||
// remove unused strings from interned strings block
|
||||
val internedBlock = program.allBlocks.singleOrNull { it.name== internedStringsModuleName }
|
||||
internedBlock?.statements?.withIndex()?.reversed()?.forEach { (index, st) ->
|
||||
if(st is VarDecl && st.scopedName !in allStringRefs) {
|
||||
internedBlock.statements.removeAt(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
if(identifier.wasStringLiteral(program)) {
|
||||
allStringRefs.add(identifier.nameInSource)
|
||||
}
|
||||
}
|
||||
|
||||
private val allStringRefs = mutableListOf<List<String>>()
|
||||
|
||||
private class Slab(val name: String, val size: Int, val align: Int, val position: Position)
|
||||
private val memorySlabs = mutableListOf<Slab>()
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ class TestGoldenRam: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.FULL,
|
||||
listOf((0x00u..0xffu)),
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = VMTarget(),
|
||||
|
||||
@@ -33,6 +33,7 @@ class TestAbstractZeropage: FunSpec({
|
||||
free.addAll(0u..255u)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
override fun allocateCx16VirtualRegisters() {
|
||||
@@ -47,6 +48,7 @@ class TestAbstractZeropage: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.FULL,
|
||||
listOf((0x50u..0x5fu)),
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = false,
|
||||
compTarget = DummyCompilationTarget,
|
||||
@@ -65,7 +67,7 @@ class TestC64Zeropage: FunSpec({
|
||||
val c64target = C64Target()
|
||||
|
||||
test("testNames") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(),
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = false,
|
||||
compTarget = c64target, loadAddress = 999u
|
||||
@@ -83,33 +85,33 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testZpFloatEnable") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
var result = zp.allocate("", DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled floats" }
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, c64target, 999u))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
result = zp2.allocate("", DataType.FLOAT, null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled ZP use" }
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target, 999u))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
zp3.allocate("", DataType.FLOAT, null, null, errors)
|
||||
}
|
||||
|
||||
test("testZpModesWithFloats") {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
}
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, c64target, 999u))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
}
|
||||
}
|
||||
|
||||
test("testZpDontuse") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
println(zp.free)
|
||||
zp.availableBytes() shouldBe 0
|
||||
val result = zp.allocate("", DataType.BYTE, null, null, errors)
|
||||
@@ -117,13 +119,13 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
zp1.availableBytes() shouldBe 17
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, c64target, 999u))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 87
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, c64target, 999u))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp3.availableBytes() shouldBe 96
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp4.availableBytes() shouldBe 207
|
||||
zp4.allocate("test", DataType.UBYTE, null, null, errors)
|
||||
zp4.availableBytes() shouldBe 206
|
||||
@@ -132,7 +134,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp1.availableBytes() shouldBe 207
|
||||
4u shouldNotBeIn zp1.free
|
||||
35u shouldNotBeIn zp1.free
|
||||
@@ -143,7 +145,7 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldBeIn zp1.free
|
||||
255u shouldBeIn zp1.free
|
||||
199u shouldBeIn zp1.free
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, c64target, 999u))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 107
|
||||
4u shouldNotBeIn zp2.free
|
||||
35u shouldNotBeIn zp2.free
|
||||
@@ -154,14 +156,14 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldNotBeIn zp2.free
|
||||
255u shouldNotBeIn zp2.free
|
||||
199u shouldBeIn zp2.free
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), false, false, c64target, 999u))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp2.availableBytes() shouldBe 107
|
||||
4u shouldBeIn zp3.free
|
||||
35u shouldNotBeIn zp3.free
|
||||
}
|
||||
|
||||
test("testBasicsafeAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 17
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
@@ -183,7 +185,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFullAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 207
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
@@ -214,7 +216,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testEfficientAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u))
|
||||
zp.availableBytes() shouldBe 17
|
||||
zp.allocate("", DataType.WORD, null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
|
||||
zp.allocate("", DataType.UBYTE, null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
|
||||
@@ -232,7 +234,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target, 999u))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
@@ -245,18 +247,18 @@ class TestCx16Zeropage: FunSpec({
|
||||
val cx16target = Cx16Target()
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, cx16target, 999u))
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, cx16target, 999u))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, cx16target, 999u))
|
||||
zp1.availableBytes() shouldBe 88
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, cx16target, 999u))
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
zp2.availableBytes() shouldBe 175
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
zp3.availableBytes() shouldBe 216
|
||||
zp3.allocate("test", DataType.UBYTE, null, null, errors)
|
||||
zp3.availableBytes() shouldBe 215
|
||||
@@ -265,7 +267,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
zp1.availableBytes() shouldBe 216
|
||||
0x22u shouldBeIn zp1.free
|
||||
0x80u shouldBeIn zp1.free
|
||||
@@ -275,7 +277,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("preallocated zp vars") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, cx16target, 999u))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u))
|
||||
zp1.allocatedVariables["test"] shouldBe null
|
||||
zp1.allocatedVariables["cx16.r0"] shouldNotBe null
|
||||
zp1.allocatedVariables["cx16.r15"] shouldNotBe null
|
||||
|
||||
@@ -366,5 +366,18 @@ main {
|
||||
compileText(VMTarget(), optimize=false, src, writeAssembly=false, errors = errors) shouldBe null
|
||||
errors.errors.single() shouldContain "cannot use byte value"
|
||||
}
|
||||
|
||||
test("const eval of address-of a memory mapped variable") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
&ubyte mappedvar = 1000
|
||||
cx16.r0 = &mappedvar
|
||||
&ubyte[8] array = &mappedvar
|
||||
cx16.r0 = &array
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import prog8.code.target.C64Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class TestArrayThings: FunSpec({
|
||||
test("assign prefix var to array should compile fine and is not split into inplace array modification") {
|
||||
@@ -147,5 +148,32 @@ main {
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("array target with expression for index") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] array = [1,2,3]
|
||||
array[cx16.r0L+1] += 42
|
||||
cx16.r0L = array[cx16.r0L+1]
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("split array in zeropage is okay") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
uword[3] @zp @split @shared thearray
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
|
||||
val assemblyFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
|
||||
val assembly = assemblyFile.readText()
|
||||
assembly shouldContain "thearray_lsb"
|
||||
assembly shouldContain "thearray_msb"
|
||||
}
|
||||
})
|
||||
|
||||
@@ -74,7 +74,7 @@ class TestAsmGenSymbols: StringSpec({
|
||||
|
||||
fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, C64Target(), 999u)
|
||||
val ptProgram = IntermediateAstMaker(program, errors).transform()
|
||||
val st = SymbolTableMaker(ptProgram, options).make()
|
||||
return AsmGen6502Internal(ptProgram, st, options, errors)
|
||||
|
||||
@@ -319,4 +319,24 @@ main
|
||||
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("inlining sub with 2 statements") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
init()
|
||||
}
|
||||
|
||||
sub init() {
|
||||
init_handler()
|
||||
return
|
||||
}
|
||||
|
||||
sub init_handler() {
|
||||
cx16.r0++
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), true, src, writeAssembly = false) shouldNotBe null
|
||||
compileText(Cx16Target(), true, src, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -1,52 +1,9 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.VarDeclOrigin
|
||||
import prog8.ast.statements.VarDeclType
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.ZeropageWish
|
||||
|
||||
|
||||
fun Program.getTempVar(dt: DataType, altNames: Boolean=false): Pair<List<String>, VarDecl> {
|
||||
val tmpvarName = if(altNames) {
|
||||
when (dt) {
|
||||
DataType.UBYTE, DataType.BOOL -> listOf("prog8_lib", "tempvar_ub2")
|
||||
DataType.BYTE -> listOf("prog8_lib", "tempvar_b2")
|
||||
DataType.UWORD -> listOf("prog8_lib", "tempvar_uw2")
|
||||
DataType.WORD -> listOf("prog8_lib", "tempvar_w2")
|
||||
DataType.FLOAT -> listOf("floats", "tempvar_swap_float2")
|
||||
else -> throw FatalAstException("invalid dt")
|
||||
}
|
||||
} else {
|
||||
when (dt) {
|
||||
DataType.UBYTE, DataType.BOOL -> listOf("prog8_lib", "tempvar_ub")
|
||||
DataType.BYTE -> listOf("prog8_lib", "tempvar_b")
|
||||
DataType.UWORD -> listOf("prog8_lib", "tempvar_uw")
|
||||
DataType.WORD -> listOf("prog8_lib", "tempvar_w")
|
||||
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
|
||||
else -> throw FatalAstException("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
val block = this.allBlocks.first { it.name==tmpvarName[0] }
|
||||
val existingDecl = block.statements.firstOrNull { it is VarDecl && it.name == tmpvarName[1] }
|
||||
if(existingDecl!=null)
|
||||
return Pair(tmpvarName, existingDecl as VarDecl)
|
||||
|
||||
// add new temp variable to the ast directly (we can do this here because we're not iterating inside those container blocks)
|
||||
val decl = VarDecl(
|
||||
VarDeclType.VAR, VarDeclOrigin.AUTOGENERATED, dt, ZeropageWish.DONTCARE,
|
||||
null, tmpvarName[1], null, isArray = false, sharedWithAsm = false, splitArray = false, position = Position.DUMMY
|
||||
)
|
||||
block.statements.add(decl)
|
||||
decl.linkParents(block)
|
||||
return Pair(tmpvarName, decl)
|
||||
}
|
||||
|
||||
fun maySwapOperandOrder(binexpr: BinaryExpression): Boolean {
|
||||
fun ok(expr: Expression): Boolean {
|
||||
return when(expr) {
|
||||
|
||||
@@ -34,7 +34,7 @@ private fun ParserRuleContext.toPosition() : Position {
|
||||
return Position(filename, start.line, start.charPositionInLine+1, start.charPositionInLine + 1 + start.stopIndex - start.startIndex)
|
||||
}
|
||||
|
||||
internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
|
||||
internal fun BlockContext.toAst(isInLibrary: Boolean) : Block {
|
||||
val blockstatements = block_statement().map {
|
||||
when {
|
||||
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
|
||||
@@ -49,10 +49,10 @@ internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
|
||||
return Block(identifier().text, integerliteral()?.toAst()?.number?.toUInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||
private fun Statement_blockContext.toAst(): MutableList<Statement> =
|
||||
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||
|
||||
private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
|
||||
private fun VariabledeclarationContext.toAst() : Statement {
|
||||
vardecl()?.let {
|
||||
return it.toAst(VarDeclType.VAR, null)
|
||||
}
|
||||
@@ -74,7 +74,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
|
||||
throw FatalAstException("weird variable decl $this")
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.SubroutinedeclarationContext.toAst() : Subroutine {
|
||||
private fun SubroutinedeclarationContext.toAst() : Subroutine {
|
||||
return when {
|
||||
subroutine()!=null -> subroutine().toAst()
|
||||
asmsubroutine()!=null -> asmsubroutine().toAst()
|
||||
@@ -83,7 +83,7 @@ private fun Prog8ANTLRParser.SubroutinedeclarationContext.toAst() : Subroutine {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
|
||||
private fun StatementContext.toAst() : Statement {
|
||||
val vardecl = variabledeclaration()?.toAst()
|
||||
if(vardecl!=null) return vardecl
|
||||
|
||||
@@ -157,7 +157,7 @@ private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
|
||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
|
||||
private fun AsmsubroutineContext.toAst(): Subroutine {
|
||||
val inline = this.inline()!=null
|
||||
val subdecl = asmsub_decl().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf()
|
||||
@@ -166,7 +166,7 @@ private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
|
||||
subdecl.asmClobbers, null, true, inline, false, statements, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.RomsubroutineContext.toAst(): Subroutine {
|
||||
private fun RomsubroutineContext.toAst(): Subroutine {
|
||||
val subdecl = asmsub_decl().toAst()
|
||||
val address = integerliteral().toAst().number.toUInt()
|
||||
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes,
|
||||
@@ -182,7 +182,7 @@ private class AsmsubDecl(val name: String,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<CpuRegister>)
|
||||
|
||||
private fun Prog8ANTLRParser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
private fun Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
val name = identifier().text
|
||||
val params = asmsub_params()?.toAst() ?: emptyList()
|
||||
val returns = asmsub_returns()?.toAst() ?: emptyList()
|
||||
@@ -204,7 +204,7 @@ private class AsmSubroutineReturn(val type: DataType,
|
||||
val registerOrPair: RegisterOrPair?,
|
||||
val statusflag: Statusflag?)
|
||||
|
||||
private fun Prog8ANTLRParser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||
private fun Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||
= asmsub_return().map {
|
||||
val register = it.register.text
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
@@ -222,7 +222,7 @@ private fun Prog8ANTLRParser.Asmsub_returnsContext.toAst(): List<AsmSubroutineRe
|
||||
statusregister)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||
private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||
= asmsub_param().map {
|
||||
val vardecl = it.vardecl()
|
||||
var datatype = vardecl.datatype()?.toAst() ?: DataType.UNDEFINED
|
||||
@@ -244,7 +244,7 @@ private fun Prog8ANTLRParser.Asmsub_paramsContext.toAst(): List<AsmSubroutinePar
|
||||
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Functioncall_stmtContext.toAst(): Statement {
|
||||
private fun Functioncall_stmtContext.toAst(): Statement {
|
||||
val void = this.VOID() != null
|
||||
val location = scoped_identifier().toAst()
|
||||
return if(expression_list() == null)
|
||||
@@ -253,7 +253,7 @@ private fun Prog8ANTLRParser.Functioncall_stmtContext.toAst(): Statement {
|
||||
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCallExpression {
|
||||
private fun FunctioncallContext.toAst(): FunctionCallExpression {
|
||||
val location = scoped_identifier().toAst()
|
||||
return if(expression_list() == null)
|
||||
FunctionCallExpression(location, mutableListOf(), toPosition())
|
||||
@@ -261,30 +261,30 @@ private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCallExpression
|
||||
FunctionCallExpression(location, expression_list().toAst().toMutableList(), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.InlineasmContext.toAst(): InlineAssembly {
|
||||
private fun InlineasmContext.toAst(): InlineAssembly {
|
||||
val text = INLINEASMBLOCK().text
|
||||
return InlineAssembly(text.substring(2, text.length-2), false, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.InlineirContext.toAst(): InlineAssembly {
|
||||
private fun InlineirContext.toAst(): InlineAssembly {
|
||||
val text = INLINEASMBLOCK().text
|
||||
return InlineAssembly(text.substring(2, text.length-2), true, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {
|
||||
private fun ReturnstmtContext.toAst() : Return {
|
||||
return Return(expression()?.toAst(), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
|
||||
private fun UnconditionaljumpContext.toAst(): Jump {
|
||||
val address = integerliteral()?.toAst()?.number?.toUInt()
|
||||
val identifier = scoped_identifier()?.toAst()
|
||||
return Jump(address, identifier, null, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.LabeldefContext.toAst(): Statement =
|
||||
private fun LabeldefContext.toAst(): Statement =
|
||||
Label(children[0].text, toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
|
||||
private fun SubroutineContext.toAst() : Subroutine {
|
||||
// non-asm subroutine
|
||||
val returntype = sub_return_part()?.datatype()?.toAst()
|
||||
return Subroutine(
|
||||
@@ -302,7 +302,7 @@ private fun Prog8ANTLRParser.SubroutineContext.toAst() : Subroutine {
|
||||
)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
vardecl().map {
|
||||
var datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
|
||||
if(it.ARRAYSIG()!=null || it.arrayindex()!=null)
|
||||
@@ -310,7 +310,7 @@ private fun Prog8ANTLRParser.Sub_paramsContext.toAst(): List<SubroutineParameter
|
||||
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Assign_targetContext.toAst() : AssignTarget {
|
||||
private fun Assign_targetContext.toAst() : AssignTarget {
|
||||
return when(this) {
|
||||
is IdentifierTargetContext ->
|
||||
AssignTarget(scoped_identifier().toAst(), null, null, scoped_identifier().toPosition())
|
||||
@@ -327,31 +327,31 @@ private fun Prog8ANTLRParser.Assign_targetContext.toAst() : AssignTarget {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ClobberContext.toAst() : Set<CpuRegister> {
|
||||
private fun ClobberContext.toAst() : Set<CpuRegister> {
|
||||
val names = this.NAME().map { it.text }
|
||||
try {
|
||||
return names.map { CpuRegister.valueOf(it) }.toSet()
|
||||
} catch(ax: IllegalArgumentException) {
|
||||
throw SyntaxError("invalid pu register", toPosition())
|
||||
throw SyntaxError("invalid cpu register", toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
|
||||
private fun DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
|
||||
|
||||
private fun Prog8ANTLRParser.ArrayindexContext.toAst() : ArrayIndex =
|
||||
private fun ArrayindexContext.toAst() : ArrayIndex =
|
||||
ArrayIndex(expression().toAst(), toPosition())
|
||||
|
||||
internal fun Prog8ANTLRParser.DirectiveContext.toAst() : Directive =
|
||||
internal fun DirectiveContext.toAst() : Directive =
|
||||
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.DirectiveargContext.toAst() : DirectiveArg {
|
||||
private fun DirectiveargContext.toAst() : DirectiveArg {
|
||||
val str = stringliteral()
|
||||
if(str?.encoding?.text!=null)
|
||||
throw SyntaxError("don't use a string encoding for directive arguments", toPosition())
|
||||
return DirectiveArg(str?.text?.substring(1, text.length-1), identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteralNode {
|
||||
private fun IntegerliteralContext.toAst(): NumericLiteralNode {
|
||||
fun makeLiteral(text: String, radix: Int): NumericLiteralNode {
|
||||
val integer: Int
|
||||
var datatype = DataType.UBYTE
|
||||
@@ -395,14 +395,14 @@ private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteralNode {
|
||||
val terminal: TerminalNode = children[0] as TerminalNode
|
||||
val integerPart = this.intpart.text
|
||||
return when (terminal.symbol.type) {
|
||||
Prog8ANTLRParser.DEC_INTEGER -> makeLiteral(integerPart, 10)
|
||||
Prog8ANTLRParser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
|
||||
Prog8ANTLRParser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
|
||||
DEC_INTEGER -> makeLiteral(integerPart, 10)
|
||||
HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
|
||||
BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
|
||||
else -> throw FatalAstException(terminal.text)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
|
||||
private fun ExpressionContext.toAst() : Expression {
|
||||
|
||||
val litval = literalvalue()
|
||||
if(litval!=null) {
|
||||
@@ -482,7 +482,7 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
|
||||
throw FatalAstException(text)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.CharliteralContext.toAst(): CharLiteral {
|
||||
private fun CharliteralContext.toAst(): CharLiteral {
|
||||
val text = this.SINGLECHAR().text
|
||||
val enc = this.encoding?.text
|
||||
val encoding =
|
||||
@@ -499,7 +499,7 @@ private fun Prog8ANTLRParser.CharliteralContext.toAst(): CharLiteral {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteral {
|
||||
private fun StringliteralContext.toAst(): StringLiteral {
|
||||
val text=this.STRING().text
|
||||
val enc = encoding?.text
|
||||
val encoding =
|
||||
@@ -516,23 +516,23 @@ private fun Prog8ANTLRParser.StringliteralContext.toAst(): StringLiteral {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Expression_listContext.toAst() = expression().map{ it.toAst() }
|
||||
private fun Expression_listContext.toAst() = expression().map{ it.toAst() }
|
||||
|
||||
private fun Prog8ANTLRParser.Scoped_identifierContext.toAst() : IdentifierReference =
|
||||
private fun Scoped_identifierContext.toAst() : IdentifierReference =
|
||||
IdentifierReference(NAME().map { it.text }, toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.FloatliteralContext.toAst() = text.toDouble()
|
||||
private fun FloatliteralContext.toAst() = text.toDouble()
|
||||
|
||||
private fun Prog8ANTLRParser.BooleanliteralContext.toAst() = when(text) {
|
||||
private fun BooleanliteralContext.toAst() = when(text) {
|
||||
"true" -> true
|
||||
"false" -> false
|
||||
else -> throw FatalAstException(text)
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.ArrayliteralContext.toAst() : Array<Expression> =
|
||||
private fun ArrayliteralContext.toAst() : Array<Expression> =
|
||||
expression().map { it.toAst() }.toTypedArray()
|
||||
|
||||
private fun Prog8ANTLRParser.If_stmtContext.toAst(): IfElse {
|
||||
private fun If_stmtContext.toAst(): IfElse {
|
||||
val condition = expression().toAst()
|
||||
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||
@@ -542,11 +542,11 @@ private fun Prog8ANTLRParser.If_stmtContext.toAst(): IfElse {
|
||||
return IfElse(condition, trueScope, elseScope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Else_partContext.toAst(): MutableList<Statement> {
|
||||
private fun Else_partContext.toAst(): MutableList<Statement> {
|
||||
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.Branch_stmtContext.toAst(): ConditionalBranch {
|
||||
private fun Branch_stmtContext.toAst(): ConditionalBranch {
|
||||
val branchcondition = branchcondition().toAst()
|
||||
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||
@@ -556,11 +556,11 @@ private fun Prog8ANTLRParser.Branch_stmtContext.toAst(): ConditionalBranch {
|
||||
return ConditionalBranch(branchcondition, trueScope, elseScope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.BranchconditionContext.toAst() = BranchCondition.valueOf(
|
||||
private fun BranchconditionContext.toAst() = BranchCondition.valueOf(
|
||||
text.substringAfter('_').uppercase()
|
||||
)
|
||||
|
||||
private fun Prog8ANTLRParser.ForloopContext.toAst(): ForLoop {
|
||||
private fun ForloopContext.toAst(): ForLoop {
|
||||
val loopvar = scoped_identifier().toAst()
|
||||
val iterable = expression()!!.toAst()
|
||||
val scope =
|
||||
@@ -571,9 +571,9 @@ private fun Prog8ANTLRParser.ForloopContext.toAst(): ForLoop {
|
||||
return ForLoop(loopvar, iterable, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.BreakstmtContext.toAst() = Break(toPosition())
|
||||
private fun BreakstmtContext.toAst() = Break(toPosition())
|
||||
|
||||
private fun Prog8ANTLRParser.WhileloopContext.toAst(): WhileLoop {
|
||||
private fun WhileloopContext.toAst(): WhileLoop {
|
||||
val condition = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
@@ -581,7 +581,7 @@ private fun Prog8ANTLRParser.WhileloopContext.toAst(): WhileLoop {
|
||||
return WhileLoop(condition, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
private fun RepeatloopContext.toAst(): RepeatLoop {
|
||||
val iterations = expression()?.toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
@@ -589,7 +589,7 @@ private fun Prog8ANTLRParser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
return RepeatLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.UnrollloopContext.toAst(): UnrollLoop {
|
||||
private fun UnrollloopContext.toAst(): UnrollLoop {
|
||||
val iterations = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
@@ -597,7 +597,7 @@ private fun Prog8ANTLRParser.UnrollloopContext.toAst(): UnrollLoop {
|
||||
return UnrollLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.UntilloopContext.toAst(): UntilLoop {
|
||||
private fun UntilloopContext.toAst(): UntilLoop {
|
||||
val untilCondition = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
@@ -605,13 +605,13 @@ private fun Prog8ANTLRParser.UntilloopContext.toAst(): UntilLoop {
|
||||
return UntilLoop(scope, untilCondition, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.WhenstmtContext.toAst(): When {
|
||||
private fun WhenstmtContext.toAst(): When {
|
||||
val condition = expression().toAst()
|
||||
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf()
|
||||
return When(condition, choices, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.When_choiceContext.toAst(): WhenChoice {
|
||||
private fun When_choiceContext.toAst(): WhenChoice {
|
||||
val values = expression_list()?.toAst()
|
||||
val stmt = statement()?.toAst()
|
||||
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
|
||||
@@ -621,7 +621,7 @@ private fun Prog8ANTLRParser.When_choiceContext.toAst(): WhenChoice {
|
||||
return WhenChoice(values?.toMutableList(), scope, toPosition())
|
||||
}
|
||||
|
||||
private fun Prog8ANTLRParser.VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
|
||||
private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
|
||||
val options = decloptions()
|
||||
val zp = when {
|
||||
options.ZEROPAGEREQUIRE().isNotEmpty() -> ZeropageWish.REQUIRE_ZEROPAGE
|
||||
|
||||
@@ -395,7 +395,15 @@ data class AddressOf(var identifier: IdentifierReference, var arrayIndex: ArrayI
|
||||
}
|
||||
|
||||
override fun copy() = AddressOf(identifier.copy(), arrayIndex?.copy(), position)
|
||||
override fun constValue(program: Program): NumericLiteral? = null
|
||||
override fun constValue(program: Program): NumericLiteral? {
|
||||
val target = this.identifier.targetStatement(program) as? VarDecl
|
||||
if(target?.type==VarDeclType.MEMORY) {
|
||||
val address = target.value?.constValue(program)
|
||||
if(address!=null)
|
||||
return NumericLiteral(DataType.UWORD, address.number, position)
|
||||
}
|
||||
return null
|
||||
}
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = identifier.nameInSource==nameInSource || arrayIndex?.referencesIdentifier(nameInSource)==true
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UWORD)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
|
||||
@@ -178,8 +178,7 @@ enum class VarDeclOrigin {
|
||||
USERCODE,
|
||||
SUBROUTINEPARAM,
|
||||
STRINGLITERAL,
|
||||
ARRAYLITERAL,
|
||||
AUTOGENERATED
|
||||
ARRAYLITERAL
|
||||
}
|
||||
|
||||
enum class VarDeclType {
|
||||
|
||||
@@ -11,12 +11,18 @@ Before you can compile Prog8 programs, you'll have to download or build the comp
|
||||
First make sure you have installed the :ref:`requirements`.
|
||||
Then you can choose a few ways to get a compiler:
|
||||
|
||||
**Download a precompiled version from github:**
|
||||
**Download an official release version from Github:**
|
||||
|
||||
#. download a recent "fat-jar" (called something like "prog8compiler-all.jar") from `the releases on Github <https://github.com/irmen/prog8/releases>`_
|
||||
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it (use the correct name and version of the jar file you've downloaded).
|
||||
|
||||
**Using the Gradle build system to build it yourself:**
|
||||
**Or, download a bleeding edge development version from Github:**
|
||||
|
||||
#. find the latest CI build on `the actions page on Github <https://github.com/irmen/prog8/actions>`_
|
||||
#. download the zipped jar artifact from that build, and unzip it.
|
||||
#. run the compiler with "java -jar prog8compiler.jar" (use the correct name and version of the jar file you've downloaded).
|
||||
|
||||
**Or, use the Gradle build system to build it yourself from source:**
|
||||
|
||||
The Gradle build system is used to build the compiler.
|
||||
The most interesting gradle commands to run are probably the ones listed below.
|
||||
|
||||
@@ -21,7 +21,7 @@ while still being low level enough to create high performance programs.
|
||||
You can compile programs for various machines with this CPU:
|
||||
|
||||
* Commodore 64
|
||||
* Commander X16 (release R42 or newer is required)
|
||||
* Commander X16
|
||||
* Commodore 128 (limited support for now)
|
||||
* Atari 800 XL (limited support for now)
|
||||
|
||||
@@ -171,7 +171,7 @@ It's easy to compile yourself, but a recent precompiled .exe (only for Windows)
|
||||
`the files section <https://sourceforge.net/projects/tass64/files/binaries/>`_ in the official project on sourceforge.
|
||||
*You need at least version 1.58.0 of this assembler.*
|
||||
If you are on a Debian based Linux, there's a "64tass" package in the repositories, which is a bit old, but it seems to work.
|
||||
It's possible to use old versions of 64tass, but it is likely that certain things will break.
|
||||
It might be possible to use old versions of 64tass, but it is likely that you'll run into assembler errors later.
|
||||
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run the prog8 compiler itself.
|
||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK or similar in their packages repository instead.
|
||||
@@ -181,7 +181,7 @@ For MacOS you can use the Homebrew system to install a recent version of OpenJDK
|
||||
Finally: an **emulator** (or a real machine of course) to test and run your programs on.
|
||||
In C64 mode, the compiler assumes the presence of the `VICE emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
If you're targeting the Commander X16 instead,
|
||||
download a recent emulator version (R42 or newer) for the CommanderX16, such as `x16emu <https://cx16forum.com/forum/viewforum.php?f=30>`_
|
||||
download a recent emulator version for the CommanderX16, such as `x16emu <https://cx16forum.com/forum/viewforum.php?f=30>`_
|
||||
(preferred, this is the official emulator. If required, source code is `here <https://github.com/X16Community/x16-emulator/>`_.
|
||||
There is also `Box16 <https://github.com/indigodarkwolf/box16>`_ which has powerful debugging features.
|
||||
You can select which one you want to launch using the ``-emu`` or ``-emu2`` command line options.
|
||||
|
||||
@@ -132,6 +132,9 @@ sys (part of syslib)
|
||||
``set_leds_brightness (ubyte activity, ubyte power)`` (commander x16 only)
|
||||
Sets the brightness of the activity and power leds on the computer.
|
||||
|
||||
``disable_caseswitch()`` and ``enable_caseswitch()``
|
||||
Disable or enable the ability to switch character set case using a keyboard combination.
|
||||
|
||||
|
||||
conv
|
||||
----
|
||||
@@ -155,6 +158,9 @@ dealing with text-based input and output (to the screen). Such as
|
||||
- scrolling the text on the screen
|
||||
- placing individual characters on the screen
|
||||
|
||||
All routines work with Screencode character encoding, except `print`, `chrout` and `input_chars`,
|
||||
these work with PETSCII encoding instead.
|
||||
|
||||
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/textio.p8>`_
|
||||
to see what's in there. (Note: slight variations for different compiler targets)
|
||||
|
||||
@@ -176,11 +182,21 @@ On the Commander X16 it tries to use that machine's fast Kernal loading routines
|
||||
Routines to directly load data into video ram are also present (vload and vload_raw).
|
||||
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
|
||||
to 16 bits, 64Kb)
|
||||
Als contains routines for operating on subdirectories (chdir, mkdir, rmdir) and to relabel the disk.
|
||||
Als contains routines for operating on subdirectories (chdir, mkdir, rmdir), to relabel the disk,
|
||||
and to seek in open files.
|
||||
|
||||
Read the `source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/diskio.p8>`_
|
||||
to see what's in there. (Note: slight variations for different compiler targets)
|
||||
|
||||
.. note::
|
||||
If you are using the X16 emulator with HostFS, and are experiencing weird behavior with these
|
||||
routines, please first try again with an SD-card image instead of HostFs.
|
||||
It is possible that there are still small differences between HostFS and actual CBM DOS in the X16 emulator.
|
||||
|
||||
.. attention::
|
||||
Error handling is peculiar on CBM dos systems (C64, C128, cx16, PET). Read the
|
||||
descriptions for the various methods in this library for details and tips.
|
||||
|
||||
|
||||
string
|
||||
------
|
||||
@@ -225,6 +241,10 @@ Provides string manipulation routines.
|
||||
Often you don't have to call this explicitly and can just write ``string1 = string2``
|
||||
but this function is useful if you're dealing with addresses for instance.
|
||||
|
||||
``append (string, suffix) -> ubyte length``
|
||||
Appends the suffix string to the other string (make sure the memory buffer is large enough!)
|
||||
Returns the length of the combined string.
|
||||
|
||||
``lower (string)``
|
||||
Lowercases the PETSCII-string in place.
|
||||
|
||||
@@ -402,6 +422,20 @@ but perhaps the provided ones can be of service too.
|
||||
If you already know the quadrant and x/y deltas, calculate discrete direction between 0 and 23.
|
||||
This is a heavily optimized routine (small and fast).
|
||||
|
||||
``diff (ubyte b1, ubyte b2) -> ubyte``
|
||||
Returns the absolute difference, or distance, between the two byte values.
|
||||
(This routine is more efficient than doing a compare and a subtract separately, or using abs)
|
||||
|
||||
``diffw (uword w1, uword w2) -> uword``
|
||||
Returns the absolute difference, or distance, between the two word values.
|
||||
(This routine is more efficient than doing a compare and a subtract separately, or using abs)
|
||||
|
||||
``mul16_last_upper () -> uword``
|
||||
Fetches the upper 16 bits of the previous 16*16 bit multiplication.
|
||||
To avoid corrupting the result, it is best performed immediately after the multiplication.
|
||||
Note: It is only for the regular 6502 cpu multiplication routine.
|
||||
It does not work for the verafx multiplication routines on the Commander X16!
|
||||
These have a different way to obtain the upper 16 bits of the result: just read cx16.r0.
|
||||
|
||||
|
||||
cx16logo
|
||||
@@ -447,8 +481,8 @@ to see what's in there.
|
||||
Information about the exposed debug registers is in the `emulator's documentation <https://github.com/X16Community/x16-emulator#debug-io-registers>`_.
|
||||
|
||||
|
||||
monogfx (cx16 only)
|
||||
--------------------
|
||||
monogfx (cx16 and virtual)
|
||||
---------------------------
|
||||
Full-screen lores or hires monochrome bitmap graphics routines, available on the Cx16 machine only.
|
||||
Same interface as gfx2, but is optimized for monochrome (1 bpp) screens.
|
||||
|
||||
@@ -531,6 +565,9 @@ the emulators already support it).
|
||||
Note: there is a block level %option "verafxmuls" that automatically replaces all word multiplications in that block
|
||||
by calls to verafx.muls/mult, but be careful with it because it may interfere with other Vera operations or IRQs.
|
||||
|
||||
Note: the lower 16 bits of the 32 bits result is returned as the normal subroutine's returnvalue,
|
||||
but the upper 16 bits is returned in `cx16.r0` so you can still access those separately.
|
||||
|
||||
``clear``
|
||||
Very quickly clear a piece of vram to a given byte value (it writes 4 bytes at a time).
|
||||
The routine is around 3 times faster as a regular unrolled loop to clear vram.
|
||||
|
||||
@@ -865,7 +865,8 @@ pokew (address, value)
|
||||
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
|
||||
|
||||
pokemon (address, value)
|
||||
Doesn't do anything useful. Also doesn't have anything to do with a certain video game.
|
||||
Like poke(), but also returns the previous value in the given address.
|
||||
Also doesn't have anything to do with a certain video game.
|
||||
|
||||
push (value)
|
||||
pushes a byte value on the CPU hardware stack. Low-level function that should normally not be used.
|
||||
|
||||
@@ -100,6 +100,12 @@ Directives
|
||||
Global setting, can occur multiple times. It allows you to reserve or 'block' a part of the zeropage so
|
||||
that it will not be used by the compiler.
|
||||
|
||||
.. data:: %zpallowed <fromaddress>,<toaddress>
|
||||
|
||||
Level: module.
|
||||
Global setting, can occur multiple times. It allows you to designate a part of the zeropage that
|
||||
the compiler is allowed to use (if other options don't prevent usage).
|
||||
|
||||
|
||||
.. data:: %address <address>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
TODO
|
||||
====
|
||||
|
||||
@@ -53,10 +54,10 @@ Compiler:
|
||||
|
||||
Libraries:
|
||||
|
||||
- gfx2 cs_innerloop640() extend number of bytes cleared, and use vera fx cached writes
|
||||
- gfx2 cs_innerloop640() extend number of bytes cleared, and use vera fx cached writes (speed up screen clear)
|
||||
- gfx2 horizontal_line and vertical_line: use vera fx cached writes
|
||||
- port monogfx, gf2, graphics to the vm
|
||||
- use verafx transparent writes to speed up pixel plotting in gfx2 and monogfx modules (avoids read/mask/write)
|
||||
- port gfx2 to the vm? First add colors to the vm?
|
||||
- fix the problems in atari target, and flesh out its libraries.
|
||||
- c128 target: make syslib more complete (missing kernal routines)?
|
||||
- pet32 target: make syslib more complete (missing kernal routines)?
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
; "unlimited sprites / bobs" demo effect.
|
||||
; Note that everything is prog8, no inline assembly used/required.
|
||||
; Note2: these aren't actual sprites, it's a bitmap backbuffer display trick.
|
||||
; TODO don't add bobs in pairs of 2 make them appear individually
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
+35
-75
@@ -6,14 +6,13 @@
|
||||
main {
|
||||
|
||||
ubyte[256] buffer = 0 to 255
|
||||
const ubyte REPEATS = 2
|
||||
|
||||
sub print_speed(uword jiffies) {
|
||||
if jiffies==0 {
|
||||
txt.print("\n 0 jiffies measured, speed is extremely high\n")
|
||||
return
|
||||
}
|
||||
float speed = 65536.0 * REPEATS / (jiffies as float / 60.0)
|
||||
float speed = floats.floor(65536.0 / (jiffies as float / 60.0))
|
||||
txt.nl()
|
||||
txt.print_uw(jiffies)
|
||||
txt.print(" jiffies = ")
|
||||
@@ -22,95 +21,56 @@ main {
|
||||
}
|
||||
|
||||
sub start() {
|
||||
txt.print("\n\ndisk benchmark. drive 8. repeats = ")
|
||||
txt.print_ub(REPEATS)
|
||||
txt.print("\n\ndisk benchmark on drive 8.\n\n")
|
||||
|
||||
uword batchtotaltime
|
||||
|
||||
txt.print("\n\nwriting 64kb using save")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
cbm.SETTIM(0,0,0)
|
||||
void diskio.save("@:benchmark.dat", $100, 32768)
|
||||
void diskio.save("@:benchmark.dat", $100, 32768)
|
||||
batchtotaltime += cbm.RDTIM16()
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
txt.print("writing 64kb using save()")
|
||||
cbm.SETTIM(0,0,0)
|
||||
; save 2 times 32Kb to make it 64Kb total
|
||||
void diskio.save("@:benchmark.dat", $100, 32768)
|
||||
void diskio.save("@:benchmark.dat", $100, 32768)
|
||||
print_speed(cbm.RDTIM16())
|
||||
|
||||
txt.print("\nwriting 64kb sequentially")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
if diskio.f_open_w("@:benchmark.dat") {
|
||||
cbm.SETTIM(0,0,0)
|
||||
repeat 65536/256 {
|
||||
if not diskio.f_write(buffer, 256)
|
||||
sys.exit(1)
|
||||
}
|
||||
batchtotaltime += cbm.RDTIM16()
|
||||
diskio.f_close_w()
|
||||
if diskio.f_open_w("@:benchmark.dat") {
|
||||
cbm.SETTIM(0,0,0)
|
||||
repeat 65536/256 {
|
||||
if not diskio.f_write(buffer, 256)
|
||||
sys.exit(1)
|
||||
}
|
||||
txt.chrout('.')
|
||||
diskio.f_close_w()
|
||||
print_speed(cbm.RDTIM16())
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
|
||||
txt.print("\nreading 64kb using load into hiram")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
cbm.SETTIM(0,0,0)
|
||||
cx16.rambank(4)
|
||||
if not diskio.load("benchmark.dat", $a000)
|
||||
sys.exit(1)
|
||||
batchtotaltime += cbm.RDTIM16()
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
txt.print("\nreading 64kb using load() into hiram")
|
||||
cbm.SETTIM(0,0,0)
|
||||
cx16.rambank(4)
|
||||
if not diskio.load("benchmark.dat", $a000)
|
||||
sys.exit(1)
|
||||
print_speed(cbm.RDTIM16())
|
||||
|
||||
txt.print("\nreading 64kb using vload into videoram")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
cbm.SETTIM(0,0,0)
|
||||
if not diskio.vload("benchmark.dat", 0, $0000)
|
||||
sys.exit(1)
|
||||
batchtotaltime += cbm.RDTIM16()
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
txt.print("\nreading 64kb using vload() into vram")
|
||||
cbm.SETTIM(0,0,0)
|
||||
if not diskio.vload("benchmark.dat", 0, $0000)
|
||||
sys.exit(1)
|
||||
print_speed(cbm.RDTIM16())
|
||||
|
||||
txt.print("\nreading 64kb sequentially")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
if diskio.f_open("benchmark.dat") {
|
||||
cbm.SETTIM(0,0,0)
|
||||
repeat 65536/255 {
|
||||
if not diskio.f_read(buffer, 255)
|
||||
sys.exit(1)
|
||||
}
|
||||
batchtotaltime += cbm.RDTIM16()
|
||||
diskio.f_close()
|
||||
if diskio.f_open("benchmark.dat") {
|
||||
cbm.SETTIM(0,0,0)
|
||||
repeat 65536/255 {
|
||||
if not diskio.f_read(buffer, 255)
|
||||
sys.exit(1)
|
||||
}
|
||||
txt.chrout('.')
|
||||
diskio.f_close()
|
||||
print_speed(cbm.RDTIM16())
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
|
||||
txt.print("\nreading 64kb sequentially (x16 optimized)")
|
||||
batchtotaltime = 0
|
||||
repeat REPEATS {
|
||||
if diskio.f_open("benchmark.dat") {
|
||||
cbm.SETTIM(0,0,0)
|
||||
repeat 65536/255 {
|
||||
if not diskio.f_read(buffer, 255)
|
||||
sys.exit(1)
|
||||
}
|
||||
batchtotaltime += cbm.RDTIM16()
|
||||
diskio.f_close()
|
||||
}
|
||||
txt.chrout('.')
|
||||
}
|
||||
print_speed(batchtotaltime)
|
||||
|
||||
txt.nl()
|
||||
txt.print(diskio.status())
|
||||
txt.print("\ndone.\n")
|
||||
|
||||
diskio.delete("benchmark.dat")
|
||||
}
|
||||
}
|
||||
+10
-11
@@ -31,17 +31,16 @@ main {
|
||||
|
||||
read_last_bytes()
|
||||
|
||||
; NOTE: f_seek_w() doesn't work reliably right now. I only manage to corrupt the fat32 filesystem on the sdcard with it...
|
||||
; txt.print("\nseeking to 1292 and writing a few bytes...\n")
|
||||
; if diskio.f_open_w("seektestfile.bin,p,a") {
|
||||
; diskio.f_seek_w(0, 1292)
|
||||
; void diskio.f_write("123", 3)
|
||||
; diskio.f_close_w()
|
||||
; } else {
|
||||
; txt.print("error: ")
|
||||
; txt.print(diskio.status())
|
||||
; sys.exit(1)
|
||||
; }
|
||||
txt.print("\nseeking to 1292 and writing a few bytes...\n")
|
||||
if diskio.f_open_w_seek("seektestfile.bin") {
|
||||
diskio.f_seek_w(0, 1292)
|
||||
void diskio.f_write("123", 3)
|
||||
diskio.f_close_w()
|
||||
} else {
|
||||
txt.print("error: ")
|
||||
txt.print(diskio.status())
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
read_last_bytes()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
%import textio
|
||||
%import diskio
|
||||
%import floats
|
||||
%import adpcm
|
||||
%import wavfile
|
||||
%option no_sysinit
|
||||
|
||||
;
|
||||
; This program can stream a regular .wav file from the sdcard.
|
||||
; It can be uncompressed or IMA-adpcm compressed (factor 4 lossy compression).
|
||||
@@ -22,33 +15,54 @@
|
||||
; is slightly more efficient because the data blocks are then sector-aligned on the disk
|
||||
;
|
||||
|
||||
%import diskio
|
||||
%import floats
|
||||
%import adpcm
|
||||
%import wavfile
|
||||
%import textio
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
|
||||
str MUSIC_FILENAME = "?"*32
|
||||
uword vera_rate_hz
|
||||
ubyte vera_rate
|
||||
|
||||
sub start() {
|
||||
uword buffer = memory("buffer", 1024, 256)
|
||||
str MUSIC_FILENAME = "?"*32
|
||||
|
||||
txt.print("name of .wav file to play on drive 8: ")
|
||||
while 0==txt.input_chars(MUSIC_FILENAME) {
|
||||
; until user types a name...
|
||||
}
|
||||
prepare_music()
|
||||
txt.print("\ngood file! playback starts! ")
|
||||
cx16.rombank(0) ; activate kernal bank for faster calls
|
||||
interrupts.wait()
|
||||
interrupts.set_handler()
|
||||
play_stuff()
|
||||
txt.print("done!\n")
|
||||
repeat { }
|
||||
}
|
||||
|
||||
bool wav_ok = false
|
||||
sub error(str msg) {
|
||||
txt.print(msg)
|
||||
repeat { }
|
||||
}
|
||||
|
||||
sub prepare_music() {
|
||||
txt.print("\nchecking ")
|
||||
txt.print(MUSIC_FILENAME)
|
||||
txt.nl()
|
||||
bool wav_ok = false
|
||||
if diskio.f_open(MUSIC_FILENAME) {
|
||||
void diskio.f_read(buffer, 128)
|
||||
wav_ok = wavfile.parse_header(buffer)
|
||||
void diskio.f_read(music.buffer, 128)
|
||||
wav_ok = wavfile.parse_header(music.buffer)
|
||||
diskio.f_close()
|
||||
}
|
||||
if not wav_ok {
|
||||
if not wav_ok
|
||||
error("no good wav file!")
|
||||
}
|
||||
|
||||
calculate_vera_rate()
|
||||
|
||||
txt.print("wav format: ")
|
||||
txt.print_ub(wavfile.wavefmt)
|
||||
txt.print("\nchannels: ")
|
||||
@@ -74,9 +88,8 @@ main {
|
||||
if wavfile.nchannels>2 or
|
||||
(wavfile.wavefmt!=wavfile.WAVE_FORMAT_DVI_ADPCM and wavfile.wavefmt!=wavfile.WAVE_FORMAT_PCM) or
|
||||
wavfile.sample_rate > 48828 or
|
||||
wavfile.bits_per_sample>16 {
|
||||
error("unsupported format!")
|
||||
}
|
||||
wavfile.bits_per_sample>16
|
||||
error("unsupported format!")
|
||||
|
||||
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM {
|
||||
if(wavfile.block_align!=256) {
|
||||
@@ -84,8 +97,6 @@ main {
|
||||
}
|
||||
}
|
||||
|
||||
txt.print("\ngood file! playback starts! ")
|
||||
cx16.rombank(0) ; activate kernal bank for faster calls
|
||||
cx16.VERA_AUDIO_RATE = 0 ; halt playback
|
||||
cx16.VERA_AUDIO_CTRL = %10101011 ; mono 16 bit, volume 11
|
||||
if wavfile.nchannels==2
|
||||
@@ -94,42 +105,6 @@ main {
|
||||
cx16.VERA_AUDIO_CTRL &= %11011111 ; set to 8 bit instead
|
||||
repeat 1024
|
||||
cx16.VERA_AUDIO_DATA = 0 ; fill buffer with short silence
|
||||
|
||||
sys.set_irqd()
|
||||
cx16.CINV = &interrupt.handler
|
||||
cx16.VERA_IEN = %00001000 ; enable AFLOW only for now
|
||||
sys.clear_irqd()
|
||||
|
||||
str progress_chars = "-\\|/-\\|/"
|
||||
ubyte progress = 0
|
||||
|
||||
if diskio.f_open(MUSIC_FILENAME) {
|
||||
uword block_size = 1024
|
||||
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM
|
||||
block_size = wavfile.block_align * 2 ; read 2 adpcm blocks at a time (512 bytes)
|
||||
void diskio.f_read(buffer, wavfile.data_offset) ; skip to actual sample data start
|
||||
void diskio.f_read(buffer, block_size) ; preload buffer
|
||||
cx16.VERA_AUDIO_RATE = vera_rate ; start playback
|
||||
repeat {
|
||||
interrupt.wait_and_clear_aflow_semaphore()
|
||||
;; cx16.vpoke(1,$fa00, $a0) ; paint a screen color
|
||||
uword size = diskio.f_read(buffer, block_size)
|
||||
;; cx16.vpoke(1,$fa00, $00) ; paint a screen color
|
||||
if size<block_size
|
||||
break
|
||||
txt.chrout(progress_chars[progress/2 & 7])
|
||||
txt.chrout($9d) ; cursor left
|
||||
progress++
|
||||
}
|
||||
diskio.f_close()
|
||||
} else {
|
||||
txt.print("load error")
|
||||
}
|
||||
|
||||
cx16.VERA_AUDIO_RATE = 0 ; halt playback
|
||||
txt.print("done!\n")
|
||||
repeat {
|
||||
}
|
||||
}
|
||||
|
||||
sub calculate_vera_rate() {
|
||||
@@ -138,51 +113,78 @@ main {
|
||||
vera_rate_hz = (vera_rate as float) * vera_freq_factor as uword
|
||||
}
|
||||
|
||||
sub error(str msg) {
|
||||
txt.print(msg)
|
||||
repeat {
|
||||
sub play_stuff() {
|
||||
if diskio.f_open(MUSIC_FILENAME) {
|
||||
uword block_size = 1024
|
||||
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM
|
||||
block_size = wavfile.block_align * 2 ; read 2 adpcm blocks at a time (512 bytes)
|
||||
void diskio.f_read(music.buffer, wavfile.data_offset) ; skip to actual sample data start
|
||||
music.pre_buffer(block_size)
|
||||
cx16.VERA_AUDIO_RATE = vera_rate ; start audio playback
|
||||
|
||||
str progress_chars = "-\\|/-\\|/"
|
||||
ubyte progress = 0
|
||||
|
||||
repeat {
|
||||
interrupts.wait()
|
||||
if interrupts.vsync {
|
||||
interrupts.vsync=false
|
||||
; ...do something triggered by vsync irq...
|
||||
}
|
||||
if interrupts.aflow {
|
||||
interrupts.aflow=false
|
||||
if not music.load_next_block(block_size)
|
||||
break
|
||||
; Note: copying the samples into the fifo buffer is done by the aflow interrupt handler itself.
|
||||
txt.chrout(progress_chars[progress/2 & 7])
|
||||
txt.chrout($9d) ; cursor left
|
||||
progress++
|
||||
}
|
||||
}
|
||||
|
||||
diskio.f_close()
|
||||
} else {
|
||||
error("load error")
|
||||
}
|
||||
|
||||
cx16.VERA_AUDIO_RATE = 0 ; halt playback
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
interrupt {
|
||||
interrupts {
|
||||
|
||||
bool aflow_semaphore
|
||||
uword @requirezp nibblesptr
|
||||
sub set_handler() {
|
||||
sys.set_irqd()
|
||||
cx16.CINV = &handler ; handles both AFLOW and VSYNC
|
||||
cx16.VERA_IEN = %00001001 ; enable AFLOW + VSYNC
|
||||
sys.clear_irqd()
|
||||
}
|
||||
|
||||
asmsub wait_and_clear_aflow_semaphore() {
|
||||
bool aflow
|
||||
bool vsync
|
||||
|
||||
asmsub wait() {
|
||||
%asm {{
|
||||
- wai
|
||||
lda p8_aflow_semaphore
|
||||
bne -
|
||||
inc p8_aflow_semaphore
|
||||
rts
|
||||
wai
|
||||
}}
|
||||
}
|
||||
|
||||
sub handler() {
|
||||
if cx16.VERA_ISR & %00001000 {
|
||||
; AFLOW irq occurred, refill buffer
|
||||
aflow_semaphore = 0
|
||||
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM {
|
||||
nibblesptr = main.start.buffer
|
||||
if wavfile.nchannels==2 {
|
||||
adpcm_block_stereo()
|
||||
adpcm_block_stereo()
|
||||
}
|
||||
else {
|
||||
adpcm_block_mono()
|
||||
adpcm_block_mono()
|
||||
}
|
||||
}
|
||||
else if wavfile.bits_per_sample==16
|
||||
uncompressed_block_16()
|
||||
else
|
||||
uncompressed_block_8()
|
||||
} else if cx16.VERA_ISR & %00000001 {
|
||||
; Filling the fifo is the only way to clear the Aflow irq.
|
||||
; So we do this here, otherwise the aflow irq will keep triggering.
|
||||
; Note that filling the buffer with fresh audio samples is NOT done here,
|
||||
; but instead in the main program code that triggers on the 'aflow' being true!
|
||||
cx16.save_virtual_registers()
|
||||
music.aflow_play_block()
|
||||
cx16.restore_virtual_registers()
|
||||
aflow = true
|
||||
}
|
||||
if cx16.VERA_ISR & %00000001 {
|
||||
cx16.VERA_ISR = %00000001
|
||||
; handle vsync irq here
|
||||
vsync = true
|
||||
}
|
||||
|
||||
%asm {{
|
||||
@@ -193,13 +195,49 @@ interrupt {
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
music {
|
||||
uword @requirezp nibblesptr
|
||||
uword buffer = memory("buffer", 1024, 256)
|
||||
|
||||
sub pre_buffer(uword block_size) {
|
||||
; pre-buffer first block
|
||||
void diskio.f_read(buffer, block_size)
|
||||
}
|
||||
|
||||
sub aflow_play_block() {
|
||||
; play block that is currently in the buffer
|
||||
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM {
|
||||
nibblesptr = buffer
|
||||
if wavfile.nchannels==2 {
|
||||
adpcm_block_stereo()
|
||||
adpcm_block_stereo()
|
||||
}
|
||||
else {
|
||||
adpcm_block_mono()
|
||||
adpcm_block_mono()
|
||||
}
|
||||
}
|
||||
else if wavfile.bits_per_sample==16
|
||||
uncompressed_block_16()
|
||||
else
|
||||
uncompressed_block_8()
|
||||
}
|
||||
|
||||
sub load_next_block(uword block_size) -> bool {
|
||||
; read next block from disk into the buffer, for next time the irq triggers
|
||||
return diskio.f_read(buffer, block_size) == block_size
|
||||
}
|
||||
|
||||
asmsub uncompressed_block_8() {
|
||||
; optimized loop to put 1024 bytes of data into the fifo as fast as possible
|
||||
; converting unsigned wav 8 bit samples to signed 8 bit on the fly
|
||||
%asm {{
|
||||
lda p8_main.p8_start.p8_buffer
|
||||
lda p8_buffer
|
||||
sta cx16.r0L
|
||||
lda p8_main.p8_start.p8_buffer+1
|
||||
lda p8_buffer+1
|
||||
sta cx16.r0H
|
||||
ldx #4
|
||||
- ldy #0
|
||||
@@ -227,9 +265,9 @@ interrupt {
|
||||
asmsub uncompressed_block_16() {
|
||||
; optimized loop to put 1024 bytes of data into the fifo as fast as possible
|
||||
%asm {{
|
||||
lda p8_main.p8_start.p8_buffer
|
||||
lda p8_buffer
|
||||
sta cx16.r0L
|
||||
lda p8_main.p8_start.p8_buffer+1
|
||||
lda p8_buffer+1
|
||||
sta cx16.r0H
|
||||
ldx #4
|
||||
- ldy #0
|
||||
@@ -284,7 +322,6 @@ interrupt {
|
||||
decode_nibbles_unrolled()
|
||||
}
|
||||
|
||||
|
||||
sub decode_nibbles_unrolled() {
|
||||
; decode 4 left channel nibbles
|
||||
uword[8] left
|
||||
|
||||
@@ -54,6 +54,6 @@ main {
|
||||
return 0
|
||||
else if screenx>255+32
|
||||
return 255
|
||||
return lsb(screenx-32)
|
||||
return lsb(screenx)-32
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,15 @@
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def make_cx16_palette(palette: list[int]) -> bytes:
|
||||
def to4bit(color: int) -> int:
|
||||
return (color * 15 + 135) >> 8 # see https://threadlocalmutex.com/?p=48
|
||||
cx16palette = bytearray()
|
||||
for pi in range(0, len(palette), 3):
|
||||
r, g, b = palette[pi] >> 4, palette[pi + 1] >> 4, palette[pi + 2] >> 4
|
||||
r = to4bit(palette[pi])
|
||||
g = to4bit(palette[pi+1])
|
||||
b = to4bit(palette[pi+2])
|
||||
cx16palette.append(g << 4 | b)
|
||||
cx16palette.append(r)
|
||||
return cx16palette
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pillow
|
||||
+20
-20
@@ -61,21 +61,21 @@ main {
|
||||
byte decisionOver2 = 1-x as byte
|
||||
|
||||
while x>=y {
|
||||
txt.setcc(xcenter + x, ycenter + y as ubyte, 81, 1)
|
||||
txt.setcc(xcenter - x, ycenter + y as ubyte, 81, 2)
|
||||
txt.setcc(xcenter + x, ycenter - y as ubyte, 81, 3)
|
||||
txt.setcc(xcenter - x, ycenter - y as ubyte, 81, 4)
|
||||
txt.setcc(xcenter + y, ycenter + x as ubyte, 81, 5)
|
||||
txt.setcc(xcenter - y, ycenter + x as ubyte, 81, 6)
|
||||
txt.setcc(xcenter + y, ycenter - x as ubyte, 81, 7)
|
||||
txt.setcc(xcenter - y, ycenter - x as ubyte, 81, 8)
|
||||
txt.setcc(xcenter + x, ycenter + y, 81, 1)
|
||||
txt.setcc(xcenter - x, ycenter + y, 81, 2)
|
||||
txt.setcc(xcenter + x, ycenter - y, 81, 3)
|
||||
txt.setcc(xcenter - x, ycenter - y, 81, 4)
|
||||
txt.setcc(xcenter + y, ycenter + x, 81, 5)
|
||||
txt.setcc(xcenter - y, ycenter + x, 81, 6)
|
||||
txt.setcc(xcenter + y, ycenter - x, 81, 7)
|
||||
txt.setcc(xcenter - y, ycenter - x, 81, 8)
|
||||
y++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*y+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
x--
|
||||
decisionOver2 += 2*(y-x)+1
|
||||
decisionOver2 -= 2*x
|
||||
}
|
||||
decisionOver2 += 2*y
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,23 +89,23 @@ main {
|
||||
while x>=y {
|
||||
xx = cx-x
|
||||
repeat 2*x+1 {
|
||||
txt.setcc(xx, cy + y as ubyte, 81, 11)
|
||||
txt.setcc(xx, cy - y as ubyte, 81, 12)
|
||||
txt.setcc(xx, cy + y, 81, 11)
|
||||
txt.setcc(xx, cy - y, 81, 12)
|
||||
xx++
|
||||
}
|
||||
xx = cx-y
|
||||
repeat 2*y+1 {
|
||||
txt.setcc(xx, cy + x as ubyte, 81, 13)
|
||||
txt.setcc(xx, cy - x as ubyte, 81, 14)
|
||||
txt.setcc(xx, cy + x, 81, 13)
|
||||
txt.setcc(xx, cy - x, 81, 14)
|
||||
xx++
|
||||
}
|
||||
y++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*y+1
|
||||
else {
|
||||
if decisionOver2>=0 {
|
||||
x--
|
||||
decisionOver2 += 2*(y-x)+1
|
||||
decisionOver2 -= 2*x
|
||||
}
|
||||
decisionOver2 += 2*y
|
||||
decisionOver2++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+120
-5
@@ -1,14 +1,129 @@
|
||||
%import textio
|
||||
%option no_sysinit
|
||||
%import textio
|
||||
%import diskio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
byte @shared bb = -44
|
||||
uword uw = 8888 + (bb as ubyte)
|
||||
txt.print_uw(uw) ; 9100
|
||||
str filename = "doesnotexist.xyz"
|
||||
ubyte status
|
||||
|
||||
|
||||
txt.print("read open...\n")
|
||||
if diskio.f_open(filename) {
|
||||
status = cbm.READST()
|
||||
txt.print("open ok - wrong! status=")
|
||||
} else {
|
||||
status = cbm.READST()
|
||||
txt.print("open failed - good! status=")
|
||||
}
|
||||
txt.print_ub(status)
|
||||
txt.nl()
|
||||
txt.print_uw(8888 + (bb as ubyte)) ; 9100
|
||||
txt.print(diskio.status())
|
||||
txt.nl()
|
||||
|
||||
txt.print("\nwriting the file\n")
|
||||
if diskio.f_open_w(filename) {
|
||||
cx16.r0 = diskio.status()
|
||||
if @(cx16.r0)=='0' {
|
||||
diskio.f_write("test", 4)
|
||||
diskio.f_close_w()
|
||||
status = cbm.READST()
|
||||
txt.print("write ok! good! status=\n")
|
||||
} else {
|
||||
txt.print("write open error, status=")
|
||||
txt.print(cx16.r0)
|
||||
txt.nl()
|
||||
}
|
||||
} else {
|
||||
status = cbm.READST()
|
||||
txt.print("write open failed! wrong! status=\n")
|
||||
}
|
||||
txt.print_ub(status)
|
||||
txt.nl()
|
||||
txt.print(diskio.status())
|
||||
txt.nl()
|
||||
|
||||
txt.print("\nread open...\n")
|
||||
if diskio.f_open(filename) {
|
||||
status = cbm.READST()
|
||||
txt.print("open ok - good! status=")
|
||||
} else {
|
||||
status = cbm.READST()
|
||||
txt.print("open failed - wrong! status=")
|
||||
}
|
||||
txt.print_ub(status)
|
||||
txt.nl()
|
||||
txt.print(diskio.status())
|
||||
txt.nl()
|
||||
}
|
||||
}
|
||||
|
||||
main33 {
|
||||
sub print_buffer() {
|
||||
uword ptr = $4000
|
||||
uword firstAA = ptr
|
||||
repeat 320*6 {
|
||||
txt.print_ubhex(@(ptr), false)
|
||||
if @(ptr)==$aa {
|
||||
firstAA = ptr
|
||||
break
|
||||
}
|
||||
ptr++
|
||||
}
|
||||
txt.nl()
|
||||
txt.print("first $aa is at ")
|
||||
txt.print_uwhex(firstAA, true)
|
||||
txt.print(" = offset ")
|
||||
txt.print_uw(firstAA-$4000)
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub start() {
|
||||
uword buffer = $4000
|
||||
sys.memset(buffer, 2000, $aa)
|
||||
|
||||
print_buffer()
|
||||
|
||||
void cx16.screen_mode($80, false) ; 320*240 * 256C
|
||||
cx16.FB_cursor_position(0, 0)
|
||||
cx16.FB_get_pixels($4000, 256)
|
||||
void cx16.screen_mode(0, false) ; 320*240 * 256C
|
||||
|
||||
print_buffer()
|
||||
repeat { }
|
||||
|
||||
|
||||
void cx16.screen_mode($80, false) ; 320*240 * 256C
|
||||
;; cx16.mouse_config2(1)
|
||||
cx16.FB_cursor_position(0, 0)
|
||||
|
||||
cx16.FB_set_pixels(0, 320*4)
|
||||
; expected result: exactly 12 rows of 320 pixels written
|
||||
; actual result: almost 13 rows of pixels written.
|
||||
|
||||
repeat {}
|
||||
|
||||
; sys.wait(9999)
|
||||
;
|
||||
; ; this works:
|
||||
; ubyte y
|
||||
; for y in 0 to 239 step 24 {
|
||||
; cx16.FB_cursor_position(0, y)
|
||||
; cx16.FB_set_pixels(0, 320*24)
|
||||
; }
|
||||
; sys.wait(120)
|
||||
;
|
||||
; ; this works too:
|
||||
; cx16.FB_cursor_position(0,0)
|
||||
; repeat 240 cx16.FB_set_pixels(0, 320)
|
||||
; sys.wait(120)
|
||||
;
|
||||
; ; this kills the mouse pointer because it writes past the bitmap screen data
|
||||
; ; expected is to write 10 times 24 rows = 240 rows of pixels, exactly 1 screen...
|
||||
; cx16.FB_cursor_position(0,0)
|
||||
; unroll 10 cx16.FB_set_pixels(0, 320*24)
|
||||
;
|
||||
; repeat {}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -4,5 +4,5 @@ org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
kotlin.code.style=official
|
||||
javaVersion=11
|
||||
kotlinVersion=1.9.10
|
||||
version=9.5.1
|
||||
kotlinVersion=1.9.20
|
||||
version=9.6
|
||||
@@ -86,6 +86,7 @@ class IRFileReader {
|
||||
var launcher = CbmPrgLauncherType.NONE
|
||||
var zeropage = ZeropageType.FULL
|
||||
val zpReserved = mutableListOf<UIntRange>()
|
||||
val zpAllowed = mutableListOf<UIntRange>()
|
||||
var loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||
var optimize = true
|
||||
var outputDir = Path("")
|
||||
@@ -112,6 +113,10 @@ class IRFileReader {
|
||||
val (zpstart, zpend) = value.split(',')
|
||||
zpReserved.add(UIntRange(zpstart.toUInt(), zpend.toUInt()))
|
||||
}
|
||||
"zpAllowed" -> {
|
||||
val (zpstart, zpend) = value.split(',')
|
||||
zpAllowed.add(UIntRange(zpstart.toUInt(), zpend.toUInt()))
|
||||
}
|
||||
"outputDir" -> outputDir = Path(value)
|
||||
"optimize" -> optimize = value.toBoolean()
|
||||
else -> throw IRParseException("illegal OPTION $name")
|
||||
@@ -124,6 +129,7 @@ class IRFileReader {
|
||||
launcher,
|
||||
zeropage,
|
||||
zpReserved,
|
||||
zpAllowed,
|
||||
false,
|
||||
false,
|
||||
target,
|
||||
|
||||
@@ -187,6 +187,9 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
for(range in irProgram.options.zpReserved) {
|
||||
xml.writeCharacters("zpReserved=${range.first},${range.last}\n")
|
||||
}
|
||||
for(range in irProgram.options.zpAllowed) {
|
||||
xml.writeCharacters("zpAllowed=${range.first},${range.last}\n")
|
||||
}
|
||||
xml.writeCharacters("loadAddress=${irProgram.options.loadAddress.toHex()}\n")
|
||||
xml.writeCharacters("optimize=${irProgram.options.optimize}\n")
|
||||
xml.writeCharacters("outputDir=${irProgram.options.outputDir.toAbsolutePath()}\n")
|
||||
|
||||
@@ -220,7 +220,7 @@ sec - set Carry status bit
|
||||
nop - do nothing
|
||||
breakpoint - trigger a breakpoint
|
||||
msig [b, w] reg1, reg2 - reg1 becomes the most significant byte (or word) of the word (or int) in reg2 (.w not yet implemented; requires 32 bits regs)
|
||||
concat [b, w] reg1, reg2, reg3 - reg1.w = concatenated lsb/lsw of reg2 (as lsb) and lsb/lsw of reg3 (as msb) into word or int (int not yet implemented; requires 32bits regs)
|
||||
concat [b, w] reg1, reg2, reg3 - reg1.w = 'concatenate' two registers: lsb/lsw of reg2 (as msb) and lsb/lsw of reg3 (as lsb) into word or int (int not yet implemented; requires 32bits regs)
|
||||
push [b, w, f] reg1 - push value in reg1 on the stack
|
||||
pop [b, w, f] reg1 - pop value from stack into reg1
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ class TestIRFileInOut: FunSpec({
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
emptyList(),
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
compTarget = target,
|
||||
|
||||
@@ -124,7 +124,7 @@ labeldef : identifier ':' ;
|
||||
unconditionaljump : 'goto' (integerliteral | scoped_identifier) ;
|
||||
|
||||
directive :
|
||||
directivename=('%output' | '%launcher' | '%zeropage' | '%zpreserved' | '%address' | '%import' |
|
||||
directivename=('%output' | '%launcher' | '%zeropage' | '%zpreserved' | '%zpallowed' | '%address' | '%import' |
|
||||
'%breakpoint' | '%asminclude' | '%asmbinary' | '%option' )
|
||||
(directivearg? | directivearg (',' directivearg)*)
|
||||
;
|
||||
@@ -188,8 +188,7 @@ expression :
|
||||
|
||||
arrayindexed:
|
||||
scoped_identifier arrayindex
|
||||
// TODO to allow chained array indexing: | arrayindexed arrayindex
|
||||
// TODO or even to allow array indexing on any uword address value: | expression arrayindex
|
||||
// TODO to allow chained/multidimensional array indexing: | arrayindexed arrayindex
|
||||
;
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<option name="HAS_STRING_ESCAPES" value="true" />
|
||||
</options>
|
||||
<keywords keywords="&;->;@;and;as;asmsub;break;clobbers;do;downto;else;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;or;repeat;return;romsub;step;sub;to;true;unroll;until;when;while;xor;~" ignore_case="false" />
|
||||
<keywords2 keywords="%address;%asm;%asmbinary;%asminclude;%breakpoint;%import;%ir;%launcher;%option;%output;%zeropage;%zpreserved;iso:;petscii:;sc:" />
|
||||
<keywords2 keywords="%address;%asm;%asmbinary;%asminclude;%breakpoint;%import;%ir;%launcher;%option;%output;%zeropage;%zpallowed;%zpreserved;iso:;petscii:;sc:" />
|
||||
<keywords3 keywords="@requirezp;@shared;@split;@zp;bool;byte;const;float;str;ubyte;uword;void;word" />
|
||||
<keywords4 keywords="abs;all;any;callfar;callram;callrom;clamp;cmp;divmod;len;lsb;max;memory;min;mkword;msb;peek;peekw;poke;pokew;pop;popw;push;pushw;reverse;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sort;sqrt;swap;|>" />
|
||||
</highlighting>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<Keywords name="Folders in comment, middle"></Keywords>
|
||||
<Keywords name="Folders in comment, close"></Keywords>
|
||||
<Keywords name="Keywords1">void const
str
byte ubyte bool
word uword
float
zp shared split requirezp</Keywords>
|
||||
<Keywords name="Keywords2">%address
%asm
%ir
%asmbinary
%asminclude
%breakpoint
%import
%launcher
%option
%output
%zeropage
%zpreserved</Keywords>
|
||||
<Keywords name="Keywords2">%address
%asm
%ir
%asmbinary
%asminclude
%breakpoint
%import
%launcher
%option
%output
%zeropage
%zpreserved
%zpallowed</Keywords>
|
||||
<Keywords name="Keywords3">inline sub asmsub romsub
clobbers
asm
if
when else
if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z
for in step do while repeat unroll
break return goto</Keywords>
|
||||
<Keywords name="Keywords4">abs all any callfar clamp cmp divmod len lsb lsl lsr memory mkword min max msb peek peekw poke pokew push pushw pop popw rsave rsavex rrestore rrestorex reverse rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sort sqrtw swap</Keywords>
|
||||
<Keywords name="Keywords5">true false
not and or xor
as to downto |></Keywords>
|
||||
|
||||
@@ -34,7 +34,7 @@ syn match prog8Label "\<\w\+\>:"
|
||||
syn keyword prog8Operator and or to downto as void
|
||||
|
||||
syn match prog8Directive "\(^\|\s\)%\(output\|launcher\|zeropage\)\>"
|
||||
syn match prog8Directive "\(^\|\s\)%\(zpreserved\|address\|import\|option\)\>"
|
||||
syn match prog8Directive "\(^\|\s\)%\(zpreserved\|zpallowed\|address\|import\|option\)\>"
|
||||
syn match prog8Directive "\(^\|\s\)%\(asmbinary\|asminclude\|breakpoint\)\>"
|
||||
syn match prog8Directive "\(^\|\s\)%\(asm\|ir\)\>"
|
||||
|
||||
|
||||
@@ -647,7 +647,6 @@ syn match prog8BuiltInFunc "\<cx16\.FB_init\>"
|
||||
syn match prog8BuiltInFunc "\<cx16\.FB_get_info\>"
|
||||
syn match prog8BuiltInFunc "\<cx16\.FB_set_palette\>"
|
||||
syn match prog8BuiltInFunc "\<cx16\.FB_cursor_position\>"
|
||||
syn match prog8BuiltInFunc "\<cx16\.FB_cursor_position2\>"
|
||||
syn match prog8BuiltInFunc "\<cx16\.FB_cursor_next_line\>"
|
||||
syn match prog8BuiltInFunc "\<cx16\.FB_get_pixel\>"
|
||||
syn match prog8BuiltInFunc "\<cx16\.FB_get_pixels\>"
|
||||
|
||||
@@ -68,9 +68,9 @@ class GraphicsWindow(val pixelWidth: Int, val pixelHeight: Int, val pixelScaling
|
||||
|
||||
fun getpixel(x: Int, y: Int): Int {
|
||||
if(x<0 || x>=pixelWidth)
|
||||
throw IllegalArgumentException("plot x outside of screen: $x")
|
||||
throw IllegalArgumentException("getpixel x outside of screen: $x")
|
||||
if(y<0 || y>=pixelHeight)
|
||||
throw IllegalArgumentException("plot y outside of screen: $y")
|
||||
throw IllegalArgumentException("getpixel y outside of screen: $y")
|
||||
return image.getRGB(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user