mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee09d35fdc | ||
|
|
af14562e2b | ||
|
|
ccc26a0eb2 | ||
|
|
e017c541bb | ||
|
|
3d5a7795d0 | ||
|
|
af06fdfffc | ||
|
|
e811ecdbde | ||
|
|
30435b6778 | ||
|
|
1358a535e3 | ||
|
|
c8f309307f | ||
|
|
b702ea50da | ||
|
|
05ba1237dc | ||
|
|
739fb6dc42 | ||
|
|
aa3f8e3038 | ||
|
|
2bbbacc46e | ||
|
|
296295011e | ||
|
|
de20c09790 | ||
|
|
4912c96f78 | ||
|
|
45cb50ebb1 | ||
|
|
2cd2bb0121 | ||
|
|
65a1300147 | ||
|
|
ba6179a4d3 | ||
|
|
6b91a464c0 | ||
|
|
3aab457a47 | ||
|
|
08b4168311 | ||
|
|
36b12e9897 | ||
|
|
9c2d8541c3 | ||
|
|
48da22866c | ||
|
|
4dedc27e5b | ||
|
|
5ba494c5e5 | ||
|
|
5c16ebe377 | ||
|
|
b5dfc0cd97 | ||
|
|
d276fb9ed5 | ||
|
|
ec9567a669 | ||
|
|
70d81298c1 | ||
|
|
1de1c10b41 | ||
|
|
25f883bb85 | ||
|
|
adc6d6626a |
No files matched your search
@@ -240,6 +240,7 @@ When writing inline assembly (`%asm {{ }}` blocks or `asmsub` routines), load th
|
||||
- **No bare `{ }` blocks** like C/Java
|
||||
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
|
||||
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
|
||||
- **Test programs for CBM targets must only use lowercase text in string literals** (the C64/C128/PET32/CX16 targets all use PETSCII). In PETSCII, uppercase letters A-Z are the *graphics* symbols, so any uppercase text you put in a string literal is rendered as unpredictable graphics characters instead of readable letters. Use only lowercase (a-z) for any printed test output, labels, or expected-match strings on these targets; reserve uppercase for intentional PETSCII graphics. The `virtual` target (ISO) is unaffected and may use either case.
|
||||
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
|
||||
- Enums: `Enum::Value` syntax (double colon), declared inside a block. They are syntactic sugar for a list of `const` declarations, not a type. Use enums for related values, `const` for standalone
|
||||
- Avoid `globals.XXXX` — move constants closer to where they're used
|
||||
|
||||
@@ -15,7 +15,7 @@ which aims to provide many conveniences over raw assembly code (even when using
|
||||
|
||||
**Want to buy me a coffee or a pizza perhaps?**
|
||||
|
||||
This project was created over the last couple of years by dedicating a huge amoung of my free time to it, to make it the best I possibly can.
|
||||
This project was created over the last couple of years by dedicating a huge amount of my free time to it, to make it the best I possibly can.
|
||||
If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,
|
||||
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong)
|
||||
|
||||
@@ -94,7 +94,7 @@ What does Prog8 provide?
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
- use a modern PC to do the work on, use nice editors and enjoy quick compilation times
|
||||
- can automatically run the program in the Vice emulator after succesful compilation
|
||||
- can automatically run the program in the Vice emulator after successful compilation
|
||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||
|
||||
|
||||
@@ -219,6 +219,11 @@ class DataType private constructor(
|
||||
|
||||
fun elementToArray(splitwords: Boolean = true): DataType {
|
||||
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
|
||||
else if(base.isPointer) {
|
||||
if(subType!=null) arrayOfPointersTo(subType)
|
||||
else if(subTypeFromAntlr!=null) arrayOfPointersFromAntlrTo(sub, subTypeFromAntlr)
|
||||
else arrayOfPointersTo(sub!!)
|
||||
}
|
||||
else arrayFor(base, false)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ enum class CpuType(val statusBitsOnMultiByteOps: Boolean) {
|
||||
* behavior consistent across all current targets.
|
||||
*/
|
||||
VIRTUAL(false)
|
||||
;
|
||||
|
||||
/** True for the 6502-family targets (plain 6502 and 65C02). */
|
||||
val is6502: Boolean
|
||||
get() = this == CPU6502 || this == CPU65C02
|
||||
}
|
||||
|
||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
|
||||
@@ -41,7 +41,7 @@ class AsmGen6502Internal (
|
||||
internal val options: CompilationOptions,
|
||||
internal val errors: IErrorReporter,
|
||||
private var generatedLabelSequenceNumber: Int,
|
||||
private val preassignedCallSiteIds: Map<PtAsmSub, UByte> = emptyMap()
|
||||
preassignedCallSiteIds: Map<PtAsmSub, UByte> = emptyMap()
|
||||
) {
|
||||
|
||||
internal val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
||||
@@ -238,12 +238,35 @@ class AsmGen6502Internal (
|
||||
return e
|
||||
}
|
||||
|
||||
// Try to get a static address for a simple variable or constant-indexed array element.
|
||||
// Returns the asm label if the expression is a variable/array with constant index,
|
||||
// or null if it is more complex. The requested variableMemSize must match the
|
||||
// variable's actual size, otherwise null is returned to force the generic path
|
||||
// that correctly handles size conversions (e.g. ubyte promoted to uword).
|
||||
fun tryGetStaticAddress(expr: PtExpression, variableMemSize: Int): String? {
|
||||
val e = unwrapCasts(expr)
|
||||
if (e is PtIdentifier) return asmVariableName(e)
|
||||
if (e is PtIdentifier) {
|
||||
val actualSize = when {
|
||||
e.type.isByte -> 1
|
||||
e.type.isWord -> 2
|
||||
e.type.isLong -> 4
|
||||
e.type.isFloat -> options.compTarget.FLOAT_MEM_SIZE
|
||||
else -> variableMemSize
|
||||
}
|
||||
if (actualSize != variableMemSize) return null
|
||||
return asmVariableName(e)
|
||||
}
|
||||
if (e is PtArrayIndexer) {
|
||||
val idx = e.index.asConstInteger()
|
||||
if (idx != null && e.variable != null) {
|
||||
val actualSize = when {
|
||||
e.type.isByte -> 1
|
||||
e.type.isWord -> 2
|
||||
e.type.isLong -> 4
|
||||
e.type.isFloat -> options.compTarget.FLOAT_MEM_SIZE
|
||||
else -> variableMemSize
|
||||
}
|
||||
if (actualSize != variableMemSize) return null
|
||||
val offset = program.memsizer.memorySize(e.type, idx)
|
||||
if (offset + (variableMemSize - 1) < 256) return "${asmVariableName(e.variable!!)}+$offset"
|
||||
}
|
||||
@@ -770,7 +793,14 @@ class AsmGen6502Internal (
|
||||
lda #$${hex.take(2)}
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
pointerGen.assignLong(IndexedPtrTarget(target), value.asConstInteger()!!)
|
||||
} else {
|
||||
TODO("assign long to array ${target.position}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> assignExpressionToRegister(value, target.register!!, true)
|
||||
TargetStorageKind.POINTER -> pointerGen.assignLong(target.pointer!!, value.number.toInt())
|
||||
@@ -793,7 +823,14 @@ class AsmGen6502Internal (
|
||||
)
|
||||
}
|
||||
|
||||
TargetStorageKind.ARRAY -> TODO("assign long to array ${target.position}")
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
pointerGen.assignLongVar(IndexedPtrTarget(target), asmSymbolName(value))
|
||||
} else {
|
||||
TODO("assign long to array ${target.position}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> assignExpressionToRegister(value, target.register!!, true)
|
||||
TargetStorageKind.POINTER -> pointerGen.assignLongVar(target.pointer!!, asmSymbolName(value))
|
||||
@@ -808,10 +845,23 @@ class AsmGen6502Internal (
|
||||
out(" lda $valuesym | sta ${target.asmVarname}")
|
||||
signExtendLongVariable(target.asmVarname, value.value.type.base)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("assign typecasted long to array ${target.position}")
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
// generic fallback: evaluate full long value into register then store via indexed pointer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12R13, true)
|
||||
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R12R13)
|
||||
} else {
|
||||
TODO("assign typecasted long to array ${target.position}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> assignExpressionToRegister(value, target.register!!, true)
|
||||
TargetStorageKind.POINTER -> TODO("assign typecasted long into pointer ${target.position}")
|
||||
TargetStorageKind.POINTER -> {
|
||||
// generic fallback for pointer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12R13, true)
|
||||
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R12R13)
|
||||
}
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else if(value.value.type.isWord) {
|
||||
@@ -824,7 +874,16 @@ class AsmGen6502Internal (
|
||||
sta ${target.asmVarname}+1""")
|
||||
signExtendLongVariable(target.asmVarname, value.value.type.base)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("assign typecasted long to array ${target.position}")
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
// generic fallback: evaluate full long value into register then store via indexed pointer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12R13, true)
|
||||
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R12R13)
|
||||
} else {
|
||||
TODO("assign typecasted long to array ${target.position}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in CombinedLongRegisters)
|
||||
@@ -836,7 +895,11 @@ class AsmGen6502Internal (
|
||||
sta cx16.$startreg+1""")
|
||||
signExtendLongVariable("cx16.$startreg", value.value.type.base)
|
||||
}
|
||||
TargetStorageKind.POINTER -> TODO("assign typecasted long into pointer ${target.position}")
|
||||
TargetStorageKind.POINTER -> {
|
||||
// generic fallback for pointer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12R13, true)
|
||||
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R12R13)
|
||||
}
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else if(value.value.type.isFloat) {
|
||||
@@ -877,7 +940,109 @@ class AsmGen6502Internal (
|
||||
}
|
||||
} else throw AssemblyError("weird casted type ${value.value.type} as ${value.type} at $value")
|
||||
} else {
|
||||
TODO("assign typecasted expression $value to a long target ${target.kind} at ${target.position} - please report this issue. Use simple expressions and temporary variables for now")
|
||||
// inner value is not a plain identifier (e.g. an array element or function result)
|
||||
when {
|
||||
value.value.type.isByte -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
assignExpressionToRegister(value.value, RegisterOrPair.A, false)
|
||||
out(" sta ${target.asmVarname}")
|
||||
signExtendLongVariable(target.asmVarname, value.value.type.base)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
// generic fallback: evaluate full long value into register then store via indexed pointer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12R13, true)
|
||||
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R12R13)
|
||||
} else {
|
||||
TODO("assign typecasted long to array ${target.position}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> assignExpressionToRegister(value, target.register!!, true)
|
||||
TargetStorageKind.POINTER -> {
|
||||
// generic fallback for pointer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12R13, true)
|
||||
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R12R13)
|
||||
}
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
value.value.type.isWord -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
assignExpressionToRegister(value.value, RegisterOrPair.AY, value.value.type.isSigned)
|
||||
out("""
|
||||
sta ${target.asmVarname}
|
||||
sty ${target.asmVarname}+1""")
|
||||
signExtendLongVariable(target.asmVarname, value.value.type.base)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
// generic fallback: evaluate full long value into register then store via indexed pointer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12R13, true)
|
||||
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R12R13)
|
||||
} else {
|
||||
TODO("assign typecasted long to array ${target.position}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val startreg = target.register!!.startregname()
|
||||
assignExpressionToRegister(value.value, RegisterOrPair.AY, value.value.type.isSigned)
|
||||
out("""
|
||||
sta cx16.$startreg
|
||||
sty cx16.$startreg+1""")
|
||||
signExtendLongVariable("cx16.$startreg", value.value.type.base)
|
||||
}
|
||||
TargetStorageKind.POINTER -> {
|
||||
// generic fallback for pointer
|
||||
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12R13, true)
|
||||
assignmentAsmGen.assignRegisterLong(target, RegisterOrPair.R12R13)
|
||||
}
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
value.value.type.isFloat -> {
|
||||
assignExpressionToRegister(value.value, RegisterOrPair.FAC1)
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta ${target.asmVarname}
|
||||
lda floats.FAC_ADDR+3
|
||||
sta ${target.asmVarname}+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val startreg = target.register!!.startregname()
|
||||
out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta cx16.$startreg
|
||||
lda floats.FAC_ADDR+3
|
||||
sta cx16.$startreg+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta cx16.$startreg+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta cx16.$startreg+3""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("assign typecasted float to long array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.POINTER -> TODO("assign typecasted float to long pointer ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird casted type ${value.value.type} as ${value.type} at $value")
|
||||
}
|
||||
}
|
||||
} else if(target.kind == TargetStorageKind.REGISTER) {
|
||||
assignExpressionToRegister(value, target.register!!, true)
|
||||
@@ -1333,7 +1498,7 @@ $repeatLabel""")
|
||||
}
|
||||
else if(swap.target1.pointerDeref!=null && swap.target2.pointerDeref!=null) {
|
||||
val v1 = swap.target1.pointerDeref!!
|
||||
val v2 = swap.target1.pointerDeref!!
|
||||
val v2 = swap.target2.pointerDeref!!
|
||||
if(v1.derefLast && v1.chain.isEmpty() && v2.derefLast && v2.chain.isEmpty() && isZpVar(v1.startpointer) && isZpVar(v2.startpointer)) {
|
||||
// optimized case where v1 and v2 are both already zeropage pointer variables
|
||||
val name1 = asmVariableName(v1.startpointer)
|
||||
@@ -1863,7 +2028,11 @@ $repeatLabel""")
|
||||
if(asm.isIR)
|
||||
throw AssemblyError("%asm containing IR code cannot be translated to 6502 assembly")
|
||||
else
|
||||
assembly.add(asm.assembly.trimEnd().trimStart('\r', '\n'))
|
||||
// User-supplied assembly should be passed through verbatim. It may contain
|
||||
// characters such as '|' (64tass bitwise OR) that out() would otherwise
|
||||
// misinterpret as a statement separator, and the user's indentation/spacing
|
||||
// should be preserved rather than re-tabbed.
|
||||
assembly.add(asm.assembly.trim('\r', '\n'))
|
||||
}
|
||||
|
||||
private fun translate(incbin: PtIncludeBinary) {
|
||||
@@ -1988,6 +2157,8 @@ $repeatLabel""")
|
||||
|
||||
internal fun isZpVar(variable: PtIdentifier): Boolean = allocator.isZpVar(variable.name)
|
||||
|
||||
internal fun isZpVar(scopedName: String): Boolean = allocator.isZpVar(scopedName)
|
||||
|
||||
internal fun jmp(asmLabel: String, indirect: Boolean=false, indexedX: Boolean=false) {
|
||||
if(indirect) {
|
||||
if(indexedX)
|
||||
|
||||
@@ -169,6 +169,18 @@ internal fun String.hasInstr(mnemonic: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun replaceMnemonic(raw: String, oldMnemonic: String, newMnemonic: String): String {
|
||||
val trimmed = raw.trimStart()
|
||||
val instruction = trimmed.instructionPart()
|
||||
if(!instruction.startsWith(oldMnemonic))
|
||||
return raw.replaceFirst(oldMnemonic, newMnemonic)
|
||||
val instrStartInTrimmed = trimmed.indexOf(instruction)
|
||||
if(instrStartInTrimmed<0) return raw.replaceFirst(oldMnemonic, newMnemonic)
|
||||
val leadingLen = raw.length - trimmed.length
|
||||
val idxInRaw = leadingLen + instrStartInTrimmed
|
||||
return raw.substring(0, idxInRaw) + newMnemonic + raw.substring(idxInRaw + oldMnemonic.length)
|
||||
}
|
||||
|
||||
/** Extracts the instruction part of a trimmed assembly line, skipping any label prefix.
|
||||
* For "mylabel: lda #1" returns "lda #1". For "lda #1" returns "lda #1". */
|
||||
private fun String.instructionPart(): String {
|
||||
@@ -673,7 +685,8 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Trimme
|
||||
}
|
||||
else if (tfirst.hasInstr("jsr") && tsecond.hasInstr("rts")) {
|
||||
if(!tfirst.contains("floats.pushFAC") && !tfirst.contains("floats.popFAC")) { // these 2 routines depend on being called with JSR!!
|
||||
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||
val replacement = replaceMnemonic(lines[0].value, "jsr", "jmp")
|
||||
mods += Modification(lines[0].index, false, replacement)
|
||||
mods += Modification(lines[1].index, true, null)
|
||||
}
|
||||
}
|
||||
@@ -750,7 +763,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Trimme
|
||||
val firstMnemonic = tfirst.take(3)
|
||||
branchInversions[firstMnemonic]?.let { invertedBranch ->
|
||||
if (sameLabel(tfirst, tsecond, tthird)) {
|
||||
val branch = lines[1].value.replace("jmp", invertedBranch)
|
||||
val branch = replaceMnemonic(lines[1].value, "jmp", invertedBranch)
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, false, branch))
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return when (fcall.name) {
|
||||
"msw" -> funcMsw(fcall, firstReturnRegister ?: RegisterOrPair.AY)
|
||||
"lsw" -> funcLsw(fcall, firstReturnRegister ?: RegisterOrPair.AY)
|
||||
"msb" -> funcMsb(fcall, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"msb__long" -> funcMsbLong(fcall, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"msb" -> funcMsb(fcall, false, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"msb__long" -> funcMsb(fcall, true, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"lsb" -> funcLsb(fcall, false, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"lsb__long" -> funcLsb(fcall,true, firstReturnRegister ?: RegisterOrPair.A)
|
||||
"mkword" -> funcMkword(fcall, firstReturnRegister ?: RegisterOrPair.AY)
|
||||
@@ -1194,7 +1194,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
val arg = fcall.args.single()
|
||||
if(arg is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(arg)
|
||||
when(val dt = arg.type.base) {
|
||||
when(arg.type.base) {
|
||||
BaseDataType.UBYTE -> {
|
||||
asmgen.out(" lda $varname | jsr prog8_lib.func_sign_ub_into_A")
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
@@ -1975,25 +1975,60 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
private fun funcClamp(fcall: PtFunctionCall): Array<RegisterOrPair> {
|
||||
val signed = fcall.type.isSigned
|
||||
val zpCheck = { name: String -> asmgen.isZpVar(name) }
|
||||
when {
|
||||
fcall.type.isByte -> {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck) && fcall.args[2].isSimple(zpCheck)) {
|
||||
// simple values (number/variable) do not clobber W1, so min/max can be evaluated first
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
|
||||
} else {
|
||||
// any non-simple argument may use W1 as a temporary, so evaluate value and minimum
|
||||
// first and preserve them while the maximum is evaluated
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value first (may use W1 as temp)
|
||||
asmgen.out(" pha")
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha")
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum (may clobber W1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1")
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}") // result in A
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
fcall.type.isWord -> {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck) && fcall.args[2].isSimple(zpCheck)) {
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
|
||||
} else {
|
||||
// any non-simple argument may use P8ZP_SCRATCH_W1/W2 as temporaries (e.g. via multiply_words/divmod_w_asm),
|
||||
// so evaluate value and minimum first and preserve them while the maximum is evaluated
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
|
||||
asmgen.out(" pha\n tya\n pha") // save AY on stack (A low, Y high)
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha\n lda P8ZP_SCRATCH_W1+1\n pha") // save minimum
|
||||
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum (may clobber W1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1+1\n pla\n sta P8ZP_SCRATCH_W1") // restore minimum
|
||||
asmgen.out(" pla\n tay\n pla") // restore AY
|
||||
}
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}") // result in AY
|
||||
return arrayOf(RegisterOrPair.AY)
|
||||
}
|
||||
fcall.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R10R11, true) // low
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R12R13, true) // high
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true) // val
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck) && fcall.args[2].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R10R11, true) // low
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R12R13, true) // high
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true) // val
|
||||
} else {
|
||||
// same ordering issue: value may clobber R10R11/R12R13 via multiply_longs/div_longs
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true) // val first
|
||||
asmgen.out(" lda cx16.r14\n pha\n lda cx16.r14+1\n pha\n lda cx16.r15\n pha\n lda cx16.r15+1\n pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R10R11, true) // low
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R12R13, true) // high
|
||||
asmgen.out(" pla\n sta cx16.r15+1\n pla\n sta cx16.r15\n pla\n sta cx16.r14+1\n pla\n sta cx16.r14")
|
||||
}
|
||||
asmgen.out(" jsr prog8_lib.func_clamp_long")
|
||||
return arrayOf(RegisterOrPair.R14R15)
|
||||
}
|
||||
@@ -2003,11 +2038,20 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
private fun funcMin(fcall: PtFunctionCall): Array<RegisterOrPair> {
|
||||
val signed = fcall.type.isSigned
|
||||
val zpCheck = { name: String -> asmgen.isZpVar(name) }
|
||||
when {
|
||||
fcall.type.isByte -> {
|
||||
if (signed) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
|
||||
} else {
|
||||
// left or right may use B1/REG as temp (e.g. multiply), so save left
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right (may clobber B1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sec
|
||||
@@ -2020,8 +2064,18 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
+ lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
|
||||
} else {
|
||||
// right in B1, left in A - left may use B1 as temp, so save right
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left (may clobber B1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
|
||||
// now A holds left, need to compare again? Actually left already in A, right in B1 restored, so cmp is still valid
|
||||
// Re-do cmp after restore is not needed, the following cmp uses P8ZP_SCRATCH_B1 correctly
|
||||
}
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" bcc +")
|
||||
asmgen.out("""
|
||||
@@ -2031,8 +2085,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
fcall.type.isWord -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha\n lda P8ZP_SCRATCH_W1+1\n pha")
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right (may clobber W1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1+1\n pla\n sta P8ZP_SCRATCH_W1")
|
||||
}
|
||||
if(signed) {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
@@ -2106,11 +2167,19 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
private fun funcMax(fcall: PtFunctionCall): Array<RegisterOrPair> {
|
||||
val signed = fcall.type.isSigned
|
||||
val zpCheck = { name: String -> asmgen.isZpVar(name) }
|
||||
when {
|
||||
fcall.type.isByte -> {
|
||||
if (signed) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right (may clobber B1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sec
|
||||
@@ -2123,8 +2192,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
+ lda P8ZP_SCRATCH_B1
|
||||
+""")
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right (may clobber B1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
|
||||
}
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" bcs +")
|
||||
asmgen.out("""
|
||||
@@ -2134,8 +2210,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
fcall.type.isWord -> {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha\n lda P8ZP_SCRATCH_W1+1\n pha")
|
||||
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right (may clobber W1)
|
||||
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1+1\n pla\n sta P8ZP_SCRATCH_W1")
|
||||
}
|
||||
if (signed) {
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
@@ -2343,55 +2426,36 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
return arrayOf(RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
private fun funcMsbLong(fcall: PtFunctionCall, resultReg: RegisterOrPair): Array<RegisterOrPair> {
|
||||
private fun funcMsb(fcall: PtFunctionCall, fromLong: Boolean, resultReg: RegisterOrPair): Array<RegisterOrPair> {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.type.isLong)
|
||||
throw AssemblyError("msb__long requires long argument")
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName+3")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+3")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+3")
|
||||
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
|
||||
}
|
||||
} else if(arg is PtArrayIndexer) {
|
||||
TODO("msb of long array element ${fcall.position}")
|
||||
if(fromLong) {
|
||||
if (!arg.type.isLong)
|
||||
throw AssemblyError("msb__long requires long argument")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.R14R15, arg.type.isSigned)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda cx16.r14+3")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx cx16.r14+3")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy cx16.r14+3")
|
||||
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
|
||||
}
|
||||
if (!arg.type.isWord && !arg.type.isPointer)
|
||||
throw AssemblyError("msb requires word argument")
|
||||
}
|
||||
|
||||
return arrayOf(resultReg)
|
||||
}
|
||||
|
||||
private fun funcMsb(fcall: PtFunctionCall, resultReg: RegisterOrPair): Array<RegisterOrPair> {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.type.isWord)
|
||||
throw AssemblyError("msb requires word argument")
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
|
||||
val offset = if(fromLong) 3 else 1
|
||||
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+1 | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+1 | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
|
||||
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName+$offset")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+$offset")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+$offset")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+$offset | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+$offset | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+$offset | ldy #0")
|
||||
else -> throw AssemblyError("invalid register for msb${if(fromLong) " long" else ""}: $resultReg ${arg.position}")
|
||||
}
|
||||
} else {
|
||||
if(arg is PtArrayIndexer) {
|
||||
if(fromLong)
|
||||
TODO("msb of long array element ${fcall.position}")
|
||||
|
||||
// just read the msb byte out of the word array
|
||||
if(arg.splitWords) {
|
||||
if(arg.variable==null)
|
||||
@@ -2413,15 +2477,15 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar,y | ldx #0")
|
||||
}
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVar,y | ldy #0")
|
||||
}
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
|
||||
asmgen.out(" ldy $arrayVar,x | ldx #0")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
|
||||
}
|
||||
} else {
|
||||
@@ -2457,6 +2521,14 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
|
||||
}
|
||||
}
|
||||
} else if(fromLong) {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.R14R15, arg.type.isSigned)
|
||||
when(resultReg) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda cx16.r14+$offset")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx cx16.r14+$offset")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy cx16.r14+$offset")
|
||||
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
|
||||
}
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
|
||||
asmgen.out(" tya")
|
||||
@@ -2471,7 +2543,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(fromLong) {
|
||||
if (!arg.type.isLong) throw AssemblyError("lsb__long requires long")
|
||||
} else {
|
||||
if (!arg.type.isWord) throw AssemblyError("lsb requires word")
|
||||
if (!arg.type.isWord && !arg.type.isPointer) throw AssemblyError("lsb requires word")
|
||||
}
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||
|
||||
@@ -377,7 +377,7 @@ internal class AssignmentAsmGen(
|
||||
throw AssemblyError("weird array element type for pointer indexed read $elementDt")
|
||||
}
|
||||
} else {
|
||||
val (zpPtrVar, _) = pointergen.deref(pointerDeref, addOffsetToPointer = true)
|
||||
val (zpPtrVar, _) = pointergen.deref(pointerDeref, addOffsetToPointer = true, loadPointerFieldValue = true)
|
||||
|
||||
when {
|
||||
elementDt.isByteOrBool -> {
|
||||
@@ -2231,6 +2231,12 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
|
||||
private fun optimizedPlusMinLongExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
|
||||
// Generic fallback for indexed pointer arrays: route to generic codegen (less efficient but avoids missing specialized cases)
|
||||
if(target.kind==TargetStorageKind.ARRAY && target.array?.pointerderef!=null) {
|
||||
asmgen.assignExpressionToRegister(expr, RegisterOrPair.R12R13, true)
|
||||
assignRegisterLong(target, RegisterOrPair.R12R13)
|
||||
return true
|
||||
}
|
||||
val left = expr.left
|
||||
val right = expr.right
|
||||
when(right) {
|
||||
@@ -2777,8 +2783,8 @@ internal class AssignmentAsmGen(
|
||||
"&" -> {
|
||||
when {
|
||||
value == 0 -> asmgen.out(" lda #0 | tay")
|
||||
value == 0x00ff -> asmgen.out(" lda #0")
|
||||
value == 0xff00 -> asmgen.out(" ldy #0")
|
||||
value == 0x00ff -> asmgen.out(" ldy #0")
|
||||
value == 0xff00 -> asmgen.out(" lda #0")
|
||||
value and 255 == 0 -> asmgen.out(" tya | and #>$value | tay | lda #0")
|
||||
value < 0x0100 -> asmgen.out(" and #<$value | ldy #0")
|
||||
else -> asmgen.out(" and #<$value | tax | tya | and #>$value | tay | txa")
|
||||
@@ -4711,7 +4717,13 @@ $endLabel""")
|
||||
else throw AssemblyError("only combined vreg allowed as long target ${target.position}")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, CpuRegister.Y)
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
// indexed pointer array via struct (e.g. foo.longs[i])
|
||||
pointergen.assignLongReg(IndexedPtrTarget(target), pairedRegisters)
|
||||
return
|
||||
}
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
|
||||
val arrayVarName = asmgen.asmSymbolName(target.array.variable!!)
|
||||
val startreg = pairedRegisters.startregname()
|
||||
// Unrolled copy to preserve array index in Y
|
||||
@@ -5940,6 +5952,7 @@ $endLabel""")
|
||||
eor #255
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.inplaceLongInvert(PtrTarget(target))
|
||||
else -> TODO("LONG INVERT ${target.kind} ${target.position}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,63 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModificationIndexedPointerField(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
|
||||
val array = target.array!!
|
||||
val elementDt = target.datatype
|
||||
if(!elementDt.isByteOrBool && !elementDt.isWord)
|
||||
throw AssemblyError("inplace modification indexed pointer field for type $elementDt not supported")
|
||||
val indexedTarget = IndexedPtrTarget(target)
|
||||
val ptr = ptrgen.indexedEffectiveAddress(indexedTarget)
|
||||
|
||||
val elementTemp = asmgen.createTempVarReused(elementDt.base, false, array)
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($ptr),y
|
||||
sta $elementTemp""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($ptr),y
|
||||
sta $elementTemp
|
||||
iny
|
||||
lda ($ptr),y
|
||||
sta $elementTemp+1""")
|
||||
}
|
||||
|
||||
// save the computed address on the hardware stack so later code can't clobber it
|
||||
asmgen.out("""
|
||||
lda $ptr
|
||||
pha
|
||||
lda $ptr+1
|
||||
pha""")
|
||||
|
||||
// reuse the existing variable-target logic to modify the temp
|
||||
val tempTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, elementDt, null, target.position, variableAsmName = elementTemp)
|
||||
inplaceModification(tempTarget, operator, value)
|
||||
|
||||
// restore the address and store the temp back
|
||||
asmgen.out("""
|
||||
pla
|
||||
sta $ptr+1
|
||||
pla
|
||||
sta $ptr""")
|
||||
if(elementDt.isByteOrBool) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $elementTemp
|
||||
sta ($ptr),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $elementTemp
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $elementTemp+1
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
|
||||
|
||||
// the asm-gen code can deal with situations where you want to assign a byte into a word.
|
||||
@@ -247,7 +304,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val deref = target.array!!.pointerderef
|
||||
if(deref!=null) {
|
||||
TODO("inplace modification array indexed pointer deref ${target.position}")
|
||||
inplaceModificationIndexedPointerField(target, operator, value)
|
||||
return
|
||||
}
|
||||
val targetArrayVar = target.array.variable!!
|
||||
|
||||
@@ -113,34 +113,126 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun inplaceByteInvert(target: PtrTarget) {
|
||||
TODO("inplace byte invert pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
val eorVal = if(target.dt.isBool) 1 else 255
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out(" lda ($zpPtrVar) | eor #$eorVal | sta ($zpPtrVar)")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #$eorVal
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceWordInvert(target: PtrTarget) {
|
||||
TODO("inplace word invert pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceLongInvert(target: PtrTarget) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceByteNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace byte negate to pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
ina
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceWordNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace word negate pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
sec
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceLongNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace long negate pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
sec
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #0
|
||||
sbc ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
internal fun inplaceFloatNegate(target: PtrTarget, scope: IPtSubroutine?) {
|
||||
TODO("inplace float negate pointer deref ${target.position}")
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
// flip sign bit in second byte of 5-byte float
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
eor #${'$'}80
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
|
||||
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false): Pair<String, UByte> {
|
||||
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false, loadPointerFieldValue: Boolean=false): Pair<String, UByte> {
|
||||
// walk the pointer deref chain and leaves the final pointer value in a ZP var
|
||||
// this will often be the temp var P8ZP_SCRATCH_PTR but can also be the original pointer variable if it is already in zeropage and there is nothing to add to it
|
||||
// returns the ZP var to use as a pointer, and a Y register offset (which can be zero)
|
||||
// loadPointerFieldValue: for addOffsetToPointer=true, also load the value if the final field is a pointer-typed field (rather than returning its address).
|
||||
|
||||
fun addFieldOffsetToScratchPointer(fieldoffset: UInt) {
|
||||
if(fieldoffset==0u)
|
||||
@@ -185,7 +277,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
if(addOffsetToPointer) {
|
||||
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
addFieldOffsetToScratchPointer(field.second.toUInt())
|
||||
if(pointer.derefLast)
|
||||
if(pointer.derefLast || (loadPointerFieldValue && field.first.isPointer))
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
} else {
|
||||
@@ -235,12 +327,59 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
} else {
|
||||
if(addOffsetToPointer) {
|
||||
addFieldOffsetToScratchPointer(fieldinfo.second.toUInt())
|
||||
if(loadPointerFieldValue && fieldinfo.first.isPointer)
|
||||
updateScratchPointer()
|
||||
return "P8ZP_SCRATCH_PTR" to 0u
|
||||
} else
|
||||
return "P8ZP_SCRATCH_PTR" to fieldinfo.second
|
||||
}
|
||||
}
|
||||
|
||||
internal fun indexedEffectiveAddress(target: IndexedPtrTarget): String {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
// generic: evaluate index as word, scale by element size, add field-corrected base pointer
|
||||
asmgen.assignExpressionToVariable(target.index, "P8ZP_SCRATCH_W1", DataType.UWORD)
|
||||
when (eltSize) {
|
||||
1 -> {}
|
||||
2 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
|
||||
4 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
|
||||
8 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
|
||||
else -> {
|
||||
// generic multiply by eltSize
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
sta prog8_math.multiply_words.multiplier
|
||||
sty prog8_math.multiply_words.multiplier+1
|
||||
lda #<$eltSize
|
||||
ldy #>$eltSize
|
||||
jsr prog8_math.multiply_words
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1""")
|
||||
}
|
||||
}
|
||||
// save scaled index to W2, compute base (field-corrected) into P8ZP_SCRATCH_PTR, then add
|
||||
asmgen.out(" lda P8ZP_SCRATCH_W1 | sta P8ZP_SCRATCH_W2 | lda P8ZP_SCRATCH_W1+1 | sta P8ZP_SCRATCH_W2+1")
|
||||
val (basePtr, _) = deref(target.pointer, addOffsetToPointer = true, loadPointerFieldValue = true)
|
||||
if(basePtr != "P8ZP_SCRATCH_PTR") {
|
||||
// deref returned the original zp pointer variable; copy it so we don't mutate it
|
||||
asmgen.out("""
|
||||
lda $basePtr
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $basePtr+1
|
||||
sta P8ZP_SCRATCH_PTR+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda P8ZP_SCRATCH_PTR
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda P8ZP_SCRATCH_PTR+1
|
||||
adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_PTR+1""")
|
||||
return "P8ZP_SCRATCH_PTR"
|
||||
}
|
||||
|
||||
internal fun assignPointerDerefExpression(target: AsmAssignTarget, value: PtPointerDeref) {
|
||||
val (zpPtrVar, offset1) = deref(value)
|
||||
val offset: UByte
|
||||
@@ -309,24 +448,24 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
internal fun inplaceModification(target: PtrTarget, operator: String, value: AsmAssignSource) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
|
||||
if(target.dt.isByte && (value.number?.number==1.0 || value.number?.number==2.0)) {
|
||||
val amount = value.number.number.toInt()
|
||||
inplaceByteInc(target, amount)
|
||||
} else {
|
||||
if (target.dt.isByte) inplaceByteAdd(target, value)
|
||||
else if (target.dt.isWord) inplaceWordAdd(target, value)
|
||||
else if (target.dt.isWord || target.dt.isPointer) inplaceWordAdd(target, value)
|
||||
else if (target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
|
||||
else if (target.dt.isLong) inplaceLongAdd(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
|
||||
if(target.dt.isByte && (value.number?.number==1.0 || value.number?.number==2.0)) {
|
||||
val amount = value.number.number.toInt()
|
||||
inplaceByteDec(target, amount)
|
||||
} else {
|
||||
if (target.dt.isByte) inplaceByteSub(target, value)
|
||||
else if (target.dt.isWord) inplaceWordSub(target, value)
|
||||
else if (target.dt.isWord || target.dt.isPointer) inplaceWordSub(target, value)
|
||||
else if (target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
|
||||
else if (target.dt.isLong) inplaceLongSub(target, value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
@@ -409,392 +548,163 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun assignByte(target: IndexedPtrTarget, byte: Int) {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
if(offset>255) {
|
||||
// need to add index to the pointer (but not clobber it) to allow index>255
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) { // TODO not very robust....
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1
|
||||
ldy #<$offset
|
||||
lda #$byte
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #<$offset
|
||||
lda #$byte
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #<$offset
|
||||
lda #$byte
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
} else if(target.index is PtIdentifier) {
|
||||
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
|
||||
val indexVarName = asmgen.asmVariableName(target.index)
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
// asmgen.loadScaledArrayIndexIntoRegister() ...
|
||||
}
|
||||
// element size is 1, can immediately add the index value
|
||||
if(target.index.type.isWord) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $zpPtrVar+1
|
||||
adc $indexVarName+1
|
||||
sta $zpPtrVar+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda #$byte
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
// asmgen.loadScaledArrayIndexIntoRegister() ...
|
||||
}
|
||||
if(target.index.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda #$byte | sta ($zpPtrVar),y")
|
||||
}
|
||||
else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.out("""
|
||||
pla
|
||||
clc
|
||||
adc $zpPtrVar+1
|
||||
sta $zpPtrVar+1""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out(" lda #$byte | sta ($zpPtrVar),y")
|
||||
}
|
||||
}
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectByte(byte, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignWord(target: IndexedPtrTarget, word: Int) {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
if(offset>255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1
|
||||
ldy #<$offset
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #<$offset
|
||||
lda #<$word
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #<$offset
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
} else if(target.index is PtIdentifier) {
|
||||
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
|
||||
val indexVarName = asmgen.asmVariableName(target.index)
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
if(target.index.type.isWord) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $zpPtrVar+1
|
||||
adc $indexVarName+1
|
||||
sta $zpPtrVar+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
if(target.index.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out("""
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.out("""
|
||||
pla
|
||||
clc
|
||||
adc $zpPtrVar+1
|
||||
sta $zpPtrVar+1""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out("""
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectWord(word, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignLong(target: IndexedPtrTarget, long: Int) {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
val hex = long.toLongHex()
|
||||
if(offset>255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1
|
||||
ldy #<$offset
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #<$offset
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #<$offset
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
} else if(target.index is PtIdentifier) {
|
||||
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
|
||||
val indexVarName = asmgen.asmVariableName(target.index)
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
if(target.index.type.isWord) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $zpPtrVar+1
|
||||
adc $indexVarName+1
|
||||
sta $zpPtrVar+1""")
|
||||
}
|
||||
val hex = long.toLongHex()
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
val hex = long.toLongHex()
|
||||
if(target.index.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out("""
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.out("""
|
||||
pla
|
||||
clc
|
||||
adc $zpPtrVar+1
|
||||
sta $zpPtrVar+1""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out("""
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
val hex = long.toLongHex()
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
|
||||
internal fun assignFloat(target: IndexedPtrTarget, float: Double) {
|
||||
// For now, only the constant-index case is supported
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
if(offset>255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
clc
|
||||
adc #<$offset
|
||||
sta $zpPtrVar
|
||||
lda $zpPtrVar+1
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1""")
|
||||
asmgen.storeIndirectFloat(float, zpPtrVar, 0.toUByte())
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
clc
|
||||
adc #<$offset
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1""")
|
||||
asmgen.storeIndirectFloat(float, "P8ZP_SCRATCH_PTR", 0.toUByte())
|
||||
}
|
||||
} else {
|
||||
asmgen.storeIndirectFloat(float, zpPtrVar, offset.toUByte())
|
||||
}
|
||||
} else {
|
||||
TODO("array ptr assign const float with variable index not yet supported ${target.position}")
|
||||
}
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectFloat(float, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignFAC1(target: IndexedPtrTarget) {
|
||||
TODO("array ptr assign FAC1 ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectFloatFP1(ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignFloatAY(target: IndexedPtrTarget) {
|
||||
TODO("array ptr assign float AY ${target.position}")
|
||||
// A/Y contains a pointer to the source float. Save it, compute destination address,
|
||||
// restore source pointer into W1, load destination pointer into A/Y, and copy.
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out(" pha | phy")
|
||||
else
|
||||
asmgen.out(" pha | tya | pha")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out("""
|
||||
ply
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $ptr
|
||||
ldy $ptr+1
|
||||
jsr floats.copy_float""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $ptr
|
||||
ldy $ptr+1
|
||||
jsr floats.copy_float""")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFloatVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign float var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectFloatVar(varName, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignByteReg(target: IndexedPtrTarget, register: CpuRegister) {
|
||||
// save register on the stack so indexedEffectiveAddress can freely use scratch regs
|
||||
asmgen.saveRegisterStack(register, false)
|
||||
TODO("array ptr assign byte reg ${target.position}")
|
||||
asmgen.saveRegisterStack(register, false)
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.restoreRegisterStack(register, false)
|
||||
asmgen.storeIndirectByteReg(register, ptr, 0u, false, false)
|
||||
}
|
||||
|
||||
internal fun assignWordReg(target: IndexedPtrTarget, regs: RegisterOrPair) {
|
||||
// save word reg on the stack so indexedEffectiveAddress can freely use scratch regs
|
||||
saveOnStack(regs)
|
||||
TODO("array ptr assign word reg ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
restoreFromStack(regs)
|
||||
asmgen.storeIndirectWordReg(regs, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignByteVar(target: IndexedPtrTarget, varName: String, extendToWord: Boolean, signed: Boolean) {
|
||||
TODO("array ptr assign byte var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
if(extendToWord) {
|
||||
// need to store byte var extended to word
|
||||
asmgen.out(" lda $varName")
|
||||
if(signed) asmgen.signExtendAXlsb(BaseDataType.BYTE) else asmgen.signExtendAXlsb(BaseDataType.UBYTE)
|
||||
asmgen.storeIndirectWordReg(RegisterOrPair.AX, ptr, 0u)
|
||||
} else {
|
||||
asmgen.storeIndirectByteVar(varName, ptr, 0u)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignWordVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign word var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.storeIndirectWordVar(varName, DataType.UWORD, ptr, 0u)
|
||||
}
|
||||
|
||||
internal fun assignLongVar(target: IndexedPtrTarget, varName: String) {
|
||||
TODO("array ptr assign long var ${target.position}")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $varName
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $varName+1
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $varName+2
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda $varName+3
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
|
||||
internal fun assignLongReg(target: IndexedPtrTarget, regs: RegisterOrPair) {
|
||||
// generic fallback for long array via pointer
|
||||
val startreg = regs.startregname()
|
||||
// save source long on the stack in case index expression evaluation uses the same registers
|
||||
asmgen.out("""
|
||||
lda cx16.$startreg
|
||||
pha
|
||||
lda cx16.$startreg+1
|
||||
pha
|
||||
lda cx16.$startreg+2
|
||||
pha
|
||||
lda cx16.$startreg+3
|
||||
pha""")
|
||||
val ptr = indexedEffectiveAddress(target)
|
||||
asmgen.out("""
|
||||
pla
|
||||
sta cx16.$startreg+3
|
||||
pla
|
||||
sta cx16.$startreg+2
|
||||
pla
|
||||
sta cx16.$startreg+1
|
||||
pla
|
||||
sta cx16.$startreg
|
||||
ldy #0
|
||||
lda cx16.$startreg
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda cx16.$startreg+1
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda cx16.$startreg+2
|
||||
sta ($ptr),y
|
||||
iny
|
||||
lda cx16.$startreg+3
|
||||
sta ($ptr),y""")
|
||||
}
|
||||
|
||||
internal fun assignLongVar(pointer: PtPointerDeref, varName: String) {
|
||||
|
||||
@@ -160,6 +160,20 @@ class TestAsmOptimizer: FunSpec({
|
||||
lines shouldBe listOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: three consecutive jsr only tail jsr+rts becomes jmp") {
|
||||
val lines = mutableListOf(" jsr \$4000", " jsr \$5000", " jsr \$6000", " rts", " nop", " nop")
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jsr \$4000", " jsr \$5000", " jmp \$6000", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: single line with embedded newlines only tail converted defensively") {
|
||||
val lines = mutableListOf(" jsr \$4000\n jsr \$5000\n jsr \$6000", " rts", " nop", " nop")
|
||||
optimize(lines)
|
||||
// Before fix this became jmp/jmp/jmp via replace("jsr","jmp"); after fix only one jmp via replaceMnemonic
|
||||
val jmpCount = lines.joinToString("\n").split("jmp").size - 1
|
||||
jmpCount shouldBe 1
|
||||
}
|
||||
|
||||
// --- optimizeUselessPushPopStack ---
|
||||
|
||||
test("optimizeUselessPushPop: removes phy+ldy+pla") {
|
||||
|
||||
@@ -224,6 +224,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
}
|
||||
"or=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = exprGen.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
@@ -232,6 +234,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
}
|
||||
"and=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = exprGen.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
@@ -249,6 +253,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
|
||||
codeGen.storeValueAtPointersLocation(inplaceInstrs, addressReg, fieldOffset, pointerDeref.type, false, oldvalueReg)
|
||||
chunks = inplaceInstrs
|
||||
} else if(array?.pointerderef != null) {
|
||||
chunks = fallbackAssign(augAssign)
|
||||
} else {
|
||||
chunks = when (augAssign.operator) {
|
||||
"+=" -> operatorPlusInplace(symbol, array, constAddress, memTarget, targetDt, value)
|
||||
@@ -385,7 +391,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
val arrayVariableName = array.variable!!.name
|
||||
val skipCarryLabel = codeGen.createLabelName()
|
||||
if(constIndex!=null) {
|
||||
val negLsbReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = negLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = negLsbReg, immediate = 0), null)
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipCarryLabel), null)
|
||||
addInstr(result, IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), null)
|
||||
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), skipCarryLabel)
|
||||
@@ -1029,6 +1039,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel)
|
||||
}
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -1289,6 +1301,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel)
|
||||
}
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -1716,6 +1730,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
val lsbReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = skip)
|
||||
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex)
|
||||
}
|
||||
@@ -1968,8 +1984,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
|
||||
if(constValue==1) {
|
||||
val skip = codeGen.createLabelName()
|
||||
val lsbReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = skip)
|
||||
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex)
|
||||
}
|
||||
|
||||
@@ -300,12 +300,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val needsExplicitCmpi = !codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
when (sourceDt.base) {
|
||||
BaseDataType.BYTE -> {
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=compareReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
|
||||
}
|
||||
@@ -317,6 +320,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val compareReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=compareReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
|
||||
}
|
||||
@@ -328,6 +333,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val compareReg = codeGen.registers.next(IRDataType.LONG)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.LONG, reg1=compareReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
|
||||
}
|
||||
|
||||
@@ -888,7 +888,10 @@ class IRCodeGen(
|
||||
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
|
||||
if(loopvarDtIr==IRDataType.BYTE && iterable.step==-1 && iterable.last==0) {
|
||||
// downto 0 optimization (byte)
|
||||
if(loopvarDt.isSignedByte || iterable.first<=127) {
|
||||
// Only rely on DECM setting status bits on targets where it actually does (6502 hardware,
|
||||
// or targets that honor the strict multi-byte status-bits contract). On the VM DECM
|
||||
// leaves flags untouched, so BSTPOS would read stale state and loop forever.
|
||||
if((loopvarDt.isSignedByte || iterable.first<=127) && (options.compTarget.cpu.is6502 || options.compTarget.cpu.statusBitsOnMultiByteOps)) {
|
||||
chunk2 += IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel)
|
||||
} else {
|
||||
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
|
||||
|
||||
@@ -67,50 +67,48 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun bitwiseOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(right.type.isIntegerOrBool) {
|
||||
val leftDt = left.type
|
||||
if(leftDt.isByteOrBool)
|
||||
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() or right.number.toInt()) and 255).toDouble(), left.position)
|
||||
else if(leftDt.isInteger) {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() or right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral(BaseDataType.LONG, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
if(left.type.isIntegerOrBool && right.type.isIntegerOrBool) {
|
||||
val resultType = bitwiseResultType(left.type, right.type)
|
||||
val result = (left.number.toInt() or right.number.toInt()) and bitwiseMask(resultType)
|
||||
return NumericLiteral(resultType, result.toDouble(), left.position)
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left | $right", left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(right.type.isIntegerOrBool) {
|
||||
val leftDt = left.type
|
||||
if(leftDt.isByteOrBool)
|
||||
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() and right.number.toInt()) and 255).toDouble(), left.position)
|
||||
else if(leftDt.isInteger) {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() and right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral(BaseDataType.LONG, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
if(left.type.isIntegerOrBool && right.type.isIntegerOrBool) {
|
||||
val resultType = bitwiseResultType(left.type, right.type)
|
||||
val result = (left.number.toInt() and right.number.toInt()) and bitwiseMask(resultType)
|
||||
return NumericLiteral(resultType, result.toDouble(), left.position)
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(right.type.isIntegerOrBool) {
|
||||
val leftDt = left.type
|
||||
if(leftDt.isByteOrBool)
|
||||
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() xor right.number.toInt()) and 255).toDouble(), left.position)
|
||||
else if(leftDt.isInteger) {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() xor right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral(BaseDataType.LONG, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
if(left.type.isIntegerOrBool && right.type.isIntegerOrBool) {
|
||||
val resultType = bitwiseResultType(left.type, right.type)
|
||||
val result = (left.number.toInt() xor right.number.toInt()) and bitwiseMask(resultType)
|
||||
return NumericLiteral(resultType, result.toDouble(), left.position)
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseResultType(type1: BaseDataType, type2: BaseDataType): BaseDataType {
|
||||
val width = if (type1.largerSizeThan(type2)) type1 else type2
|
||||
return when {
|
||||
width.isByteOrBool -> BaseDataType.UBYTE
|
||||
width.isWord -> BaseDataType.UWORD
|
||||
width == BaseDataType.LONG -> BaseDataType.LONG
|
||||
else -> BaseDataType.UBYTE
|
||||
}
|
||||
}
|
||||
|
||||
private fun bitwiseMask(type: BaseDataType): Int = when(type) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> 0xFF
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> 0xFFFF
|
||||
else -> 0xFFFFFFFF.toInt()
|
||||
}
|
||||
|
||||
private fun logicalAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral =
|
||||
NumericLiteral.fromBoolean(left.asBooleanValue and right.asBooleanValue, left.position)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class VarConstantValueTypeAdjuster(
|
||||
if (decl.value != null && !isConstantInitializer(decl.value!!))
|
||||
return noModifications
|
||||
if (decl.value != null && isMemorySlabInit(decl.value!!))
|
||||
return noModifications // a pointer to a memory slab is usually accessed often and/or in ways that benefit more from the pointer being an actual variable (preferrably in zeropage)
|
||||
return noModifications // a pointer to a memory slab is usually accessed often and/or in ways that benefit more from the pointer being an actual variable (preferably in zeropage)
|
||||
val usages = callGraph.usages(decl)
|
||||
val (writes, reads) = usages
|
||||
.partition {
|
||||
|
||||
@@ -11,6 +11,8 @@ import prog8.code.target.VMTarget
|
||||
|
||||
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.values.isEmpty()
|
||||
|
||||
private class CannotInlineException : Exception()
|
||||
|
||||
/**
|
||||
* The Inliner performs subroutine inlining on the AST.
|
||||
*
|
||||
@@ -87,6 +89,13 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
fun isBodyInlineable(stmt: Assignment): Boolean {
|
||||
// Never auto-inline a sub that assigns directly to one of its parameters.
|
||||
// Substituting the call argument into the assignment target would either
|
||||
// crash (literal argument has no identifier to assign to) or change semantics
|
||||
// (assigning to the caller's variable instead of the local parameter copy).
|
||||
val targetName = stmt.target.identifier?.nameInSource?.lastOrNull()
|
||||
if (targetName != null && subroutine.parameters.any { it.name == targetName })
|
||||
return false
|
||||
return if (stmt.value.isSimple) {
|
||||
val targetInline =
|
||||
if (stmt.target.identifier != null) {
|
||||
@@ -139,7 +148,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
stmt.target is NumericLiteral
|
||||
}
|
||||
|
||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.size <= 1) {
|
||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.size <= 1 && subroutine.parameters.all { it.registerOrPair == null }) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is Subroutine || (it is VarDecl && it.origin != VarDeclOrigin.SUBROUTINEPARAM) }
|
||||
if (!containsSubsOrVariables) {
|
||||
val functionalStatements = subroutine.statements.filter { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
|
||||
@@ -254,11 +263,23 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
return false to "subroutines with more than 1 parameter cannot be inlined in this compiler version"
|
||||
}
|
||||
|
||||
if (sub.parameters.any { it.registerOrPair != null }) {
|
||||
return false to "subroutines with register-mapped parameters cannot be inlined"
|
||||
}
|
||||
|
||||
if (sub.parameters.size == 1) {
|
||||
val arg = fcall.args.first()
|
||||
if (!arg.isSimple) {
|
||||
return false to "argument is too complex to inline (might have side effects)"
|
||||
}
|
||||
// Refuse to inline a sub that assigns directly to its parameter.
|
||||
// Substituting the argument into the target would crash for a literal
|
||||
// argument, or wrongly modify the caller's variable for a variable argument
|
||||
// (parameters are passed by value, so the sub only modifies its local copy).
|
||||
val paramName = sub.parameters[0].name
|
||||
val bodyStmt = sub.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
|
||||
if ((bodyStmt as? Assignment)?.target?.identifier?.nameInSource?.lastOrNull() == paramName)
|
||||
return false to "cannot inline subroutine that assigns to its parameter"
|
||||
}
|
||||
|
||||
if (options.compTarget.name != VMTarget.NAME) {
|
||||
@@ -304,9 +325,9 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
fun inlineFunctionBody(toInline: Return): Iterable<AstModification> {
|
||||
// call site is an expression, so we have to have a Return here in the inlined sub to provide the values
|
||||
return if (toInline.values.size == 1 && functionCallExpr !== toInline.values[0]) {
|
||||
logInlining(sub, functionCallExpr)
|
||||
val substitutedReturn = substituteParameters(sub, functionCallExpr, toInline.values[0]) as? Expression
|
||||
?: return noModifications
|
||||
sub.hasBeenInlined = true
|
||||
val substitutedReturn = substituteParameters(sub, functionCallExpr, toInline.values[0]) as Expression
|
||||
listOf(AstReplaceNode(functionCallExpr, substitutedReturn, parent))
|
||||
} else
|
||||
noModifications
|
||||
@@ -327,18 +348,17 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
|
||||
fun possiblyShortCircuitFunctionCall(toInline: Return): Iterable<AstModification> {
|
||||
// Substitute parameters in the return values first
|
||||
val substitutedReturn = substituteParameters(sub, origNode as IFunctionCall, toInline) as Return
|
||||
val substitutedReturn = substituteParameters(sub, origNode as IFunctionCall, toInline) as? Return
|
||||
?: return noModifications
|
||||
val functionCalls = substitutedReturn.values.filterIsInstance<FunctionCallExpression>()
|
||||
|
||||
if (functionCalls.isEmpty()) {
|
||||
// No function calls in the return values - the void call has no side effects and can be removed.
|
||||
logInlining(sub, origNode)
|
||||
sub.hasBeenInlined = true
|
||||
return listOf(AstRemove(origNode as Statement, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
// There are function calls in the return values - convert each to a void statement.
|
||||
logInlining(sub, origNode)
|
||||
sub.hasBeenInlined = true
|
||||
|
||||
val voidStatements: MutableList<Statement> = functionCalls.map { fcall ->
|
||||
@@ -356,10 +376,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
fun possiblyInlineFunctionBody(toInline: Statement): Iterable<AstModification> {
|
||||
val inlinedStatement = substituteParameters(sub, origNode as IFunctionCall, toInline) as Statement
|
||||
val inlinedStatement = substituteParameters(sub, origNode as IFunctionCall, toInline) as? Statement
|
||||
?: return noModifications
|
||||
|
||||
return if (origNode !== toInline) {
|
||||
logInlining(sub, origNode)
|
||||
sub.hasBeenInlined = true
|
||||
listOf(AstReplaceNode(origNode, inlinedStatement, parent))
|
||||
} else
|
||||
@@ -391,13 +411,6 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
|
||||
private fun logInlining(sub: Subroutine, call: Node) {
|
||||
// TODO remove this logging once we are sure everything works good
|
||||
if (sub.parameters.isNotEmpty() && !options.quiet) {
|
||||
println(">>> INLINER: inlining '${sub.name}' at ${call.position} (subroutine at ${sub.position})")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSimpleReturnExpression(expr: Expression): Boolean {
|
||||
return when (expr) {
|
||||
is NumericLiteral -> true
|
||||
@@ -436,7 +449,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
return noModifications
|
||||
|
||||
// Substitute parameters in all return values
|
||||
val substitutedReturn = substituteParameters(sub, fcall, toInline) as Return
|
||||
val substitutedReturn = substituteParameters(sub, fcall, toInline) as? Return
|
||||
?: return noModifications
|
||||
|
||||
// Create multiple single assignments, skipping void targets (they discard the value)
|
||||
val newAssignments = multiTargets.zip(substitutedReturn.values)
|
||||
@@ -463,7 +477,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
return listOf(AstReplaceNode(assignment, scope, parent))
|
||||
}
|
||||
|
||||
private fun substituteParameters(sub: Subroutine, fcall: IFunctionCall, node: Node): Node {
|
||||
private fun substituteParameters(sub: Subroutine, fcall: IFunctionCall, node: Node): Node? {
|
||||
val paramVarDecls = sub.parameters.map { param ->
|
||||
sub.statements.filterIsInstance<VarDecl>().find { it.origin == VarDeclOrigin.SUBROUTINEPARAM && it.name == param.name }
|
||||
?: throw FatalAstException("parameter ${param.name} not found in subroutine ${sub.name}")
|
||||
@@ -486,7 +500,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
is ArrayIndexedExpression -> ArrayIndexedExpression(
|
||||
n.plainarrayvar?.let {
|
||||
val subId = substitute(it)
|
||||
if (subId !is IdentifierReference) throw FatalAstException("parameter substituted by ${subId::class.simpleName} but an identifier reference was expected")
|
||||
if (subId !is IdentifierReference) throw CannotInlineException()
|
||||
subId
|
||||
},
|
||||
n.nestedArray?.let { substitute(it) as ArrayIndexedExpression },
|
||||
@@ -500,7 +514,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
val newTarget = n.target.copy()
|
||||
newTarget.identifier = n.target.identifier?.let {
|
||||
val subId = substitute(it)
|
||||
if (subId !is IdentifierReference) throw FatalAstException("parameter substituted by ${subId::class.simpleName} but an identifier reference was expected")
|
||||
if (subId !is IdentifierReference) throw CannotInlineException()
|
||||
subId
|
||||
}
|
||||
newTarget.memoryAddress = n.target.memoryAddress?.let { DirectMemoryWrite(substitute(it.addressExpression) as Expression, it.position) }
|
||||
@@ -523,7 +537,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
is FunctionCallExpression -> FunctionCallExpression(
|
||||
substitute(n.target).let {
|
||||
if (it !is IdentifierReference) throw FatalAstException("parameter substituted by ${it::class.simpleName} but an identifier reference was expected")
|
||||
if (it !is IdentifierReference) throw CannotInlineException()
|
||||
it
|
||||
},
|
||||
n.args.map { substitute(it) as Expression }.toMutableList(),
|
||||
@@ -531,7 +545,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
)
|
||||
is FunctionCallStatement -> FunctionCallStatement(
|
||||
substitute(n.target).let {
|
||||
if (it !is IdentifierReference) throw FatalAstException("parameter substituted by ${it::class.simpleName} but an identifier reference was expected")
|
||||
if (it !is IdentifierReference) throw CannotInlineException()
|
||||
it
|
||||
},
|
||||
n.args.map { substitute(it) as Expression }.toMutableList(),
|
||||
@@ -547,7 +561,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
is AddressOf -> AddressOf(
|
||||
n.identifier?.let {
|
||||
val subId = substitute(it)
|
||||
if (subId !is IdentifierReference) throw FatalAstException("parameter substituted by ${subId::class.simpleName} but an identifier reference was expected")
|
||||
if (subId !is IdentifierReference) throw CannotInlineException()
|
||||
subId
|
||||
},
|
||||
n.arrayIndex?.let { substitute(it) as ArrayIndex },
|
||||
@@ -560,6 +574,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
}
|
||||
|
||||
return substitute(node)
|
||||
return try {
|
||||
substitute(node)
|
||||
} catch (_: CannotInlineException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,16 +349,28 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled.
|
||||
; Overdraws horizontal lines unfortunately.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
|
||||
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)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*2+1)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*2+1)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -367,6 +379,10 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*2+1)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*2+1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -446,4 +462,3 @@ _y_lookup_hi .byte >_plot_y_values
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -439,12 +439,25 @@ gfx_hires {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -453,6 +466,10 @@ gfx_hires {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
@@ -462,12 +479,25 @@ gfx_hires {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
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)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -476,6 +506,10 @@ gfx_hires {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, ubyte @zp color) {
|
||||
|
||||
@@ -501,11 +501,24 @@ gfx_lores {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -514,6 +527,10 @@ gfx_lores {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
@@ -523,6 +540,9 @@ gfx_lores {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
while radius>=yy {
|
||||
uword liney = ycenter+yy
|
||||
@@ -532,12 +552,22 @@ gfx_lores {
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-radius, lsb(liney), radius*$0002+1, color)
|
||||
|
||||
liney = ycenter+radius
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-yy, lsb(liney), yy*$0002+1, color)
|
||||
liney = ycenter-radius
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-yy, lsb(liney), yy*$0002+1, color)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
liney = ycenter+pendingRadius
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-pendingWidth, lsb(liney), pendingWidth*$0002+1, color)
|
||||
liney = ycenter-pendingRadius
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-pendingWidth, lsb(liney), pendingWidth*$0002+1, color)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
@@ -547,6 +577,14 @@ gfx_lores {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
uword flushLiney = ycenter+pendingRadius
|
||||
if msb(flushLiney)==0
|
||||
safe_horizontal_line(xcenter-pendingWidth, lsb(flushLiney), pendingWidth*$0002+1, color)
|
||||
flushLiney = ycenter-pendingRadius
|
||||
if msb(flushLiney)==0
|
||||
safe_horizontal_line(xcenter-pendingWidth, lsb(flushLiney), pendingWidth*$0002+1, color)
|
||||
}
|
||||
}
|
||||
|
||||
asmsub plot(uword x @AX, ubyte y @Y, ubyte color @R0) {
|
||||
|
||||
@@ -604,17 +604,29 @@ drawmode: ora cx16.r15L
|
||||
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
|
||||
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
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)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -623,22 +635,38 @@ drawmode: ora cx16.r15L
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Does bounds checking and clipping.
|
||||
; Midpoint algorithm, filled
|
||||
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
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)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -647,6 +675,10 @@ drawmode: ora cx16.r15L
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
|
||||
|
||||
@@ -1744,8 +1744,8 @@ asmsub restore_irq() clobbers(A) {
|
||||
sta cx16.via1ier
|
||||
; set timer1 back to free running mode
|
||||
lda cx16.via1acr
|
||||
and %00111111
|
||||
ora %01000000
|
||||
and #%00111111
|
||||
ora #%01000000
|
||||
sta cx16.via1acr
|
||||
lda #$ff
|
||||
sta cx16.via1t1ll
|
||||
|
||||
@@ -304,11 +304,24 @@ monogfx {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
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)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -317,6 +330,10 @@ monogfx {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
@@ -326,11 +343,24 @@ monogfx {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
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)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -339,6 +369,10 @@ monogfx {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
|
||||
|
||||
@@ -2,7 +2,6 @@ package prog8
|
||||
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.AstException
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.source.ImportFileSystem
|
||||
import prog8.code.source.ImportFileSystem.expandTilde
|
||||
import prog8.code.target.CompilationTargets
|
||||
@@ -221,6 +220,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
breakpointCpuInstruction,
|
||||
printAst1 == true,
|
||||
printAst2 == true,
|
||||
printCompileInfo = true,
|
||||
ignoreFootguns == true,
|
||||
profilingInstrumentation == true,
|
||||
traceImports == true,
|
||||
@@ -306,11 +306,12 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
breakpointCpuInstruction,
|
||||
printAst1 == true,
|
||||
printAst2 == true,
|
||||
true,
|
||||
ignoreFootguns == true,
|
||||
profilingInstrumentation == true,
|
||||
traceImports == true,
|
||||
processedSymbols,
|
||||
absoluteSrcDirs,
|
||||
traceImports == true,
|
||||
processedSymbols,
|
||||
absoluteSrcDirs,
|
||||
outputPath,
|
||||
cwd = Path.of(System.getProperty("user.dir")),
|
||||
errors = ErrorReporter(txtcolors)
|
||||
@@ -371,6 +372,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
breakpointCpuInstruction,
|
||||
printAst1 == true,
|
||||
printAst2 == true,
|
||||
true,
|
||||
ignoreFootguns == true,
|
||||
profilingInstrumentation == true,
|
||||
traceImports == true,
|
||||
@@ -705,6 +707,7 @@ private fun compileViaDaemon(compilerArgs: CompilerArguments, plainText: Boolean
|
||||
println("Using existing prog8c daemon at $socketPath")
|
||||
}
|
||||
|
||||
printCompileStart(compilerArgs)
|
||||
val response = communicateWithDaemon(channel, compilerArgs, plainText)
|
||||
if (response != null && (response.ok || !wasExisting || response.versionError == null)) return response
|
||||
|
||||
@@ -725,6 +728,15 @@ private fun compileViaDaemon(compilerArgs: CompilerArguments, plainText: Boolean
|
||||
return communicateWithDaemon(channel, compilerArgs, plainText)
|
||||
}
|
||||
|
||||
private fun printCompileStart(args: CompilerArguments) {
|
||||
if (args.quietAll)
|
||||
return
|
||||
val programPath = args.filepath.toAbsolutePath().normalize()
|
||||
val display = args.cwd.toAbsolutePath().relativize(programPath)
|
||||
println("Compiling program $display")
|
||||
println("Compiler target: ${getCompilationTargetByName(args.compilationTarget).name}")
|
||||
}
|
||||
|
||||
private fun startDaemonProcess(): Boolean {
|
||||
return try {
|
||||
val javaHome = System.getProperty("java.home")
|
||||
@@ -784,6 +796,7 @@ private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: Compiler
|
||||
|
||||
val request = DaemonRequest(
|
||||
version = prog8.buildversion.BUILD_UNIX_TIME.toString(),
|
||||
plainText = plainText,
|
||||
filepath = compilerArgs.filepath.toString(),
|
||||
optimize = compilerArgs.optimize,
|
||||
writeAssembly = compilerArgs.writeAssembly,
|
||||
@@ -836,22 +849,9 @@ private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: Compiler
|
||||
return response
|
||||
}
|
||||
|
||||
val txtcolors = if(plainText) ErrorReporter.PlainText else ErrorReporter.AnsiColors
|
||||
val reporter = ErrorReporter(txtcolors)
|
||||
for (error in response.errors) {
|
||||
val pos = Position(error.file ?: "", error.line, error.startCol, error.endCol)
|
||||
when (error.severity) {
|
||||
"ERROR" -> reporter.err(error.message, pos)
|
||||
"WARNING" -> reporter.warn(error.message, pos)
|
||||
"INFO" -> reporter.info(error.message, pos)
|
||||
}
|
||||
}
|
||||
reporter.report()
|
||||
|
||||
System.out.print(response.stdout)
|
||||
System.out.flush()
|
||||
System.err.print(response.stderr)
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
|
||||
return response
|
||||
|
||||
@@ -65,6 +65,7 @@ class CompilerArguments(val filepath: Path,
|
||||
val breakpointCpuInstruction: String?,
|
||||
val printAst1: Boolean,
|
||||
val printAst2: Boolean,
|
||||
val printCompileInfo: Boolean = true,
|
||||
val ignoreFootguns: Boolean,
|
||||
val profilingInstrumentation: Boolean,
|
||||
val traceImports: Boolean = false,
|
||||
@@ -112,8 +113,9 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
libraryDirs,
|
||||
args.cwd,
|
||||
args.quietAll,
|
||||
args.printCompileInfo,
|
||||
args.traceImports
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val (program, options, imported) = parseresult
|
||||
@@ -411,12 +413,13 @@ fun parseMainModule(filepath: Path,
|
||||
libraryDirs: List<String>,
|
||||
cwd: Path,
|
||||
quiet: Boolean,
|
||||
traceImports: Boolean): Triple<Program, CompilationOptions, List<Path>> {
|
||||
printCompileInfo: Boolean = true,
|
||||
traceImports: Boolean = false): Triple<Program, CompilationOptions, List<Path>> {
|
||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
|
||||
bf.program = program
|
||||
|
||||
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs, libraryDirs, cwd, quiet, traceImports)
|
||||
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs, libraryDirs, cwd, quiet, printCompileInfo, traceImports)
|
||||
val importedModuleResult = importer.importMainModule(filepath)
|
||||
importedModuleResult.onErr { throw it }
|
||||
errors.report()
|
||||
@@ -535,6 +538,8 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
|
||||
}
|
||||
|
||||
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
program.checkVarDeclsOnOwnLine(errors)
|
||||
errors.report()
|
||||
program.preprocessAst(errors, compilerOptions)
|
||||
if(errors.noErrors() && compilerOptions.dumpSymbols) {
|
||||
printSymbols(program)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.buildversion.BUILD_UNIX_TIME
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.source.ImportFileSystem
|
||||
import prog8.code.source.SourceCode
|
||||
import java.io.*
|
||||
import java.net.StandardProtocolFamily
|
||||
import java.net.UnixDomainSocketAddress
|
||||
@@ -104,7 +103,6 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
val resp = DaemonResponse(
|
||||
ok = false,
|
||||
versionError = "daemon version mismatch: build time differs",
|
||||
errors = emptyList(),
|
||||
stdout = "",
|
||||
stderr = "",
|
||||
t_ms = 0,
|
||||
@@ -127,17 +125,16 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
System.setOut(combinedStream)
|
||||
System.setErr(combinedStream)
|
||||
|
||||
val daemonErr = DaemonErrorReporter()
|
||||
val reporter = ErrorReporter(if (request.plainText) ErrorReporter.PlainText else ErrorReporter.AnsiColors)
|
||||
var result: CompilationResult? = null
|
||||
ImportFileSystem.clearCaches()
|
||||
val t_ms = try {
|
||||
measureTime {
|
||||
val compilerArgs = request.toCompilerArguments(daemonErr)
|
||||
val compilerArgs = request.toCompilerArguments(reporter)
|
||||
result = compileProgram(compilerArgs)
|
||||
}.toLong(DurationUnit.MILLISECONDS)
|
||||
} catch (e: Exception) {
|
||||
System.err.println("daemon: compilation crash: ${e.message}")
|
||||
daemonErr.err("internal daemon error: ${e.message}", Position.DUMMY)
|
||||
0L
|
||||
} finally {
|
||||
System.out.flush()
|
||||
@@ -169,35 +166,6 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
val resp = DaemonResponse(
|
||||
ok = result != null,
|
||||
versionError = null,
|
||||
errors = daemonErr.getMessages().map { msg ->
|
||||
// Strip "ERROR ", "WARNING ", "INFO " prefixes
|
||||
var message = if (msg.message.startsWith("${msg.severity} ")) {
|
||||
msg.message.substring(msg.severity.length + 1)
|
||||
} else {
|
||||
msg.message
|
||||
}
|
||||
|
||||
// Strip path prefix if it exists (e.g. file:///...:line:col: )
|
||||
// The path prefix usually ends with a colon followed by a space
|
||||
val pathRegex = Regex("^file:///[^:]+:[0-9]+:[0-9]+: ")
|
||||
message = message.replace(pathRegex, "")
|
||||
|
||||
DaemonError(
|
||||
severity = msg.severity,
|
||||
message = message,
|
||||
file = msg.position?.let {
|
||||
val origin = it.file
|
||||
if (SourceCode.isLibraryResource(origin)) {
|
||||
origin
|
||||
} else {
|
||||
ImportFileSystem.userHome.resolve(origin).normalize().toString()
|
||||
}
|
||||
},
|
||||
line = msg.position?.line ?: 1,
|
||||
startCol = msg.position?.startCol ?: 1,
|
||||
endCol = msg.position?.endCol ?: 1
|
||||
)
|
||||
},
|
||||
stdout = stdout,
|
||||
stderr = stderr,
|
||||
t_ms = t_ms,
|
||||
@@ -214,7 +182,7 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun DaemonRequest.toCompilerArguments(daemonErr: DaemonErrorReporter): CompilerArguments {
|
||||
private fun DaemonRequest.toCompilerArguments(errors: IErrorReporter): CompilerArguments {
|
||||
// Resolve the output directory path: if relative, make it absolute by resolving against
|
||||
// the client's working directory (cwd) to avoid writing files in the daemon's CWD.
|
||||
val clientCwd = Path.of(cwd)
|
||||
@@ -244,6 +212,7 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
breakpointCpuInstruction = breakpointCpuInstruction,
|
||||
printAst1 = printAst1,
|
||||
printAst2 = printAst2,
|
||||
printCompileInfo = false,
|
||||
ignoreFootguns = ignoreFootguns,
|
||||
profilingInstrumentation = profilingInstrumentation,
|
||||
traceImports = traceImports,
|
||||
@@ -251,7 +220,7 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
sourceDirs = sourceDirs,
|
||||
outputDir = resolvedOutputDir,
|
||||
cwd = clientCwd,
|
||||
errors = daemonErr
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.code.core.ErrorsReportedException
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
|
||||
|
||||
internal class DaemonErrorReporter : IErrorReporter {
|
||||
data class CompilerMessage(val severity: String, val message: String, val position: Position?)
|
||||
|
||||
private val messages = mutableListOf<CompilerMessage>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
messages.add(CompilerMessage("ERROR", msg, position))
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
messages.add(CompilerMessage("WARNING", msg, position))
|
||||
}
|
||||
|
||||
override fun info(msg: String, position: Position) {
|
||||
messages.add(CompilerMessage("INFO", msg, position))
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
|
||||
if (suggestImport && symbol.size > 1)
|
||||
err("undefined symbol: ${symbol.joinToString(".")} (maybe you forgot to import a module that defines ${symbol.first()}?)", position)
|
||||
else
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors() = messages.none { it.severity == "ERROR" }
|
||||
|
||||
override fun report() {
|
||||
// Don't print anything, but throw so compileProgram() stops and returns null
|
||||
if (!noErrors())
|
||||
throw ErrorsReportedException("")
|
||||
}
|
||||
|
||||
override fun noErrorForLine(position: Position) =
|
||||
!messages.any { it.position?.line == position.line && it.severity != "INFO" }
|
||||
|
||||
override fun printSingleError(errormessage: String) {
|
||||
messages.add(CompilerMessage("ERROR", errormessage, null))
|
||||
}
|
||||
|
||||
fun getMessages() = messages.toList()
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package prog8.compiler
|
||||
|
||||
internal data class DaemonRequest(
|
||||
val version: String,
|
||||
val plainText: Boolean,
|
||||
val filepath: String,
|
||||
val optimize: Boolean,
|
||||
val writeAssembly: Boolean,
|
||||
@@ -35,7 +36,6 @@ internal data class DaemonRequest(
|
||||
internal data class DaemonResponse(
|
||||
val ok: Boolean,
|
||||
val versionError: String?,
|
||||
val errors: List<DaemonError>,
|
||||
val stdout: String,
|
||||
val stderr: String,
|
||||
val t_ms: Long,
|
||||
@@ -43,15 +43,6 @@ internal data class DaemonResponse(
|
||||
val importedFiles: List<String>
|
||||
)
|
||||
|
||||
internal data class DaemonError(
|
||||
val severity: String,
|
||||
val message: String,
|
||||
val file: String?,
|
||||
val line: Int,
|
||||
val startCol: Int,
|
||||
val endCol: Int
|
||||
)
|
||||
|
||||
|
||||
// Minimal JSON encoder/decoder for the daemon protocol
|
||||
internal object DaemonProtocol {
|
||||
@@ -59,6 +50,7 @@ internal object DaemonProtocol {
|
||||
fun encodeRequest(req: DaemonRequest): String = buildString {
|
||||
append('{')
|
||||
append(prop("version", req.version))
|
||||
append(prop("plainText", req.plainText))
|
||||
append(prop("filepath", req.filepath))
|
||||
append(prop("optimize", req.optimize))
|
||||
append(prop("writeAssembly", req.writeAssembly))
|
||||
@@ -95,21 +87,6 @@ internal object DaemonProtocol {
|
||||
append('{')
|
||||
append(prop("ok", resp.ok))
|
||||
append(propOpt("versionError", resp.versionError))
|
||||
append("\"errors\":[")
|
||||
for ((i, e) in resp.errors.withIndex()) {
|
||||
if (i > 0) append(',')
|
||||
append('{')
|
||||
append(prop("severity", e.severity))
|
||||
append(prop("message", e.message))
|
||||
append(propOpt("file", e.file))
|
||||
append(prop("line", e.line))
|
||||
append(prop("startCol", e.startCol))
|
||||
append(prop("endCol", e.endCol))
|
||||
append("null")
|
||||
setLength(length - 4)
|
||||
append('}')
|
||||
}
|
||||
append("],")
|
||||
append(prop("stdout", resp.stdout))
|
||||
append(prop("stderr", resp.stderr))
|
||||
append(prop("t_ms", resp.t_ms))
|
||||
@@ -122,24 +99,11 @@ internal object DaemonProtocol {
|
||||
|
||||
fun decodeResponse(json: String): DaemonResponse {
|
||||
val map = parseJsonObject(json)
|
||||
val errorsJson = map["errors"] as? List<*> ?: emptyList<Any>()
|
||||
val errors = errorsJson.map { e ->
|
||||
val em = e as Map<*, *>
|
||||
DaemonError(
|
||||
severity = em["severity"] as String,
|
||||
message = em["message"] as String,
|
||||
file = em["file"] as? String,
|
||||
line = (em["line"] as Number).toInt(),
|
||||
startCol = (em["startCol"] as Number).toInt(),
|
||||
endCol = (em["endCol"] as Number).toInt()
|
||||
)
|
||||
}
|
||||
val outputFiles = (map["outputFiles"] as? List<*>)?.map { it as String } ?: emptyList()
|
||||
val outputFiles = (map["outputFiles"] as? List<*>)?.map { it as String } ?: emptyList<String>()
|
||||
val importedFiles = (map["importedFiles"] as? List<*>)?.map { it as String } ?: emptyList()
|
||||
return DaemonResponse(
|
||||
ok = map["ok"] as Boolean,
|
||||
versionError = map["versionError"] as? String,
|
||||
errors = errors,
|
||||
stdout = map["stdout"] as String,
|
||||
stderr = map["stderr"] as String,
|
||||
t_ms = (map["t_ms"] as Number).toLong(),
|
||||
@@ -152,6 +116,7 @@ internal object DaemonProtocol {
|
||||
val map = parseJsonObject(json)
|
||||
return DaemonRequest(
|
||||
version = map["version"] as String,
|
||||
plainText = map["plainText"] as Boolean,
|
||||
filepath = map["filepath"] as String,
|
||||
optimize = map["optimize"] as Boolean,
|
||||
writeAssembly = map["writeAssembly"] as Boolean,
|
||||
@@ -266,10 +231,12 @@ internal object DaemonProtocol {
|
||||
|
||||
private fun parseStringValue(s: String, start: Int): Pair<String, Int> {
|
||||
check(s[start] == '"')
|
||||
pos@ for (pos in (start + 1) until s.length) {
|
||||
var pos = start + 1
|
||||
while (pos < s.length) {
|
||||
when (s[pos]) {
|
||||
'\\' -> continue@pos // skip next char
|
||||
'\\' -> pos += 2
|
||||
'"' -> return unescapeJson(s.substring(start + 1, pos)) to (pos + 1)
|
||||
else -> pos++
|
||||
}
|
||||
}
|
||||
error("unterminated string in JSON")
|
||||
|
||||
@@ -26,6 +26,7 @@ class ModuleImporter(private val program: Program,
|
||||
libraryDirs: List<String>,
|
||||
val cwd: Path,
|
||||
val quiet: Boolean,
|
||||
val printCompileInfo: Boolean = true,
|
||||
val traceImports: Boolean = false) {
|
||||
|
||||
private val sourcePaths: List<Path> = sourceDirs.map { Path(it).sanitize() }.distinct()
|
||||
@@ -61,7 +62,7 @@ class ModuleImporter(private val program: Program,
|
||||
}
|
||||
|
||||
private fun printCompileInfo(programPath: Path) {
|
||||
if(!quiet) {
|
||||
if(!quiet && printCompileInfo) {
|
||||
println("Compiling program ${cwd.toAbsolutePath().relativize(programPath)}")
|
||||
println("Compiler target: $compilationTargetName")
|
||||
}
|
||||
@@ -175,17 +176,20 @@ class ModuleImporter(private val program: Program,
|
||||
return result.mapError { NoSuchFileException(File(name)) }
|
||||
}
|
||||
|
||||
private fun getModuleFromFile(name: String, importingModule: Module?): Pair<Result<SourceCode, NoSuchFileException>, List<Path>> {
|
||||
internal fun getModuleFromFile(name: String, importingModule: Module?): Pair<Result<SourceCode, NoSuchFileException>, List<Path>> {
|
||||
val fileName = "$name.p8"
|
||||
val requestedBy = importingModule?.name ?: "~implicit~"
|
||||
|
||||
val cwd = Path("").absolute()
|
||||
val normalLocations =
|
||||
if (importingModule == null) {
|
||||
(sourcePaths + listOf(Path("").absolute())).distinct()
|
||||
} else {
|
||||
(sourcePaths + listOf(cwd)).distinct()
|
||||
} else if (SourceCode.isRegularFilesystemPath(importingModule.position.file)) {
|
||||
val pathFromImportingModule = (Path(importingModule.position.file).parent ?: Path("")).sanitize()
|
||||
val cwd = Path("").absolute()
|
||||
(sourcePaths + listOf(pathFromImportingModule, cwd)).distinct()
|
||||
} else {
|
||||
// library/string resources have no neighboring directory on disk
|
||||
(sourcePaths + listOf(cwd)).distinct()
|
||||
}
|
||||
|
||||
val libraryPathsSet = libraryPaths.toSet()
|
||||
|
||||
@@ -2071,7 +2071,7 @@ internal class AstChecker(private val program: Program,
|
||||
private fun checkPointer(pointervar: IdentifierReference) {
|
||||
val vardecl = pointervar.targetVarDecl()
|
||||
if(vardecl?.zeropage == ZeropageWish.NOT_IN_ZEROPAGE)
|
||||
errors.info("pointer variable should preferrably be in zeropage but is marked nozp", vardecl.position)
|
||||
errors.info("pointer variable should preferably be in zeropage but is marked nozp", vardecl.position)
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
@@ -2136,6 +2136,23 @@ internal class AstChecker(private val program: Program,
|
||||
if(!dt.isPointer && !dt.isUnsignedWord && !dt.isIterable) {
|
||||
errors.err("cannot array index on this field type", arrayIndexedExpression.indexer.position)
|
||||
}
|
||||
// check index out of bounds for struct field arrays via pointer
|
||||
val constIdx = arrayIndexedExpression.indexer.constIndex()
|
||||
if(constIdx!=null) {
|
||||
val deref = arrayIndexedExpression.pointerderef!!
|
||||
val target = deref.definingScope.lookup(deref.chain)
|
||||
val arraysize: Int? = when(target) {
|
||||
is StructFieldRef -> target.field.constSize()
|
||||
is VarDecl -> target.arraysize?.constIndex()
|
||||
else -> null
|
||||
}
|
||||
if(arraysize!=null) {
|
||||
if(constIdx !in 0..<arraysize)
|
||||
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
} else if(constIdx < 0) {
|
||||
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
}
|
||||
}
|
||||
// else if(target is StructFieldRef) {
|
||||
// if(!target.type.isPointer && !target.type.isUnsignedWord)
|
||||
// errors.err("cannot array index on this field type", arrayIndexedExpression.indexer.position)
|
||||
@@ -2314,14 +2331,28 @@ internal class AstChecker(private val program: Program,
|
||||
if(uniqueFields.isEmpty())
|
||||
errors.err("struct must contain at least one field", struct.position)
|
||||
|
||||
fun validFieldType(field: StructField): Boolean {
|
||||
val dt=field.type
|
||||
if(dt.isArray && dt.subType!=null) {
|
||||
errors.err("arrays of typed pointers are currently not supported, use untyped pointer for now at field '${field.name}'", struct.position)
|
||||
return false
|
||||
}
|
||||
return dt.isBasic
|
||||
|| dt.base.isNumericOrBool
|
||||
|| dt.isPointer
|
||||
|| dt.isArray
|
||||
}
|
||||
|
||||
struct.fields.forEach { field ->
|
||||
val dt = field.type
|
||||
val allowed = dt.isBasic
|
||||
|| dt.base.isNumericOrBool
|
||||
|| dt.isPointer
|
||||
|| (dt.isArray && dt.sub!!.isNumericOrBool)
|
||||
if(!allowed)
|
||||
errors.err("only booleans, numeric and pointer fields allowed in a struct: '${field.name}'", struct.position)
|
||||
if(!validFieldType(field))
|
||||
errors.err("invalid struct field type at '${field.name}'", struct.position)
|
||||
if(field.isArray) {
|
||||
val size = field.constSize()
|
||||
if(size==null)
|
||||
errors.err("array length must be known at compile-time", field.arraySize?.position ?: struct.position)
|
||||
else if(size<=0)
|
||||
errors.err("array length must be positive", field.arraySize?.position ?: struct.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2723,7 +2754,7 @@ internal class AstChecker(private val program: Program,
|
||||
val struct = initializer.structname.targetStructDecl()
|
||||
if(struct!=null) {
|
||||
val expectedFieldCount = struct.fields.size
|
||||
val expectedFlattenedElements = struct.fields.sumOf { it.arraySize ?: 1 }
|
||||
val expectedFlattenedElements = struct.fields.sumOf { it.constSize() ?: 1 }
|
||||
val flattenedArgs = args.flattenArgs()
|
||||
|
||||
if (args.size != expectedFieldCount) {
|
||||
|
||||
@@ -5,10 +5,7 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstModification
|
||||
import prog8.ast.walk.AstRemove
|
||||
import prog8.ast.walk.AstReplaceNode
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.*
|
||||
import prog8.code.core.*
|
||||
import java.io.CharConversionException
|
||||
|
||||
@@ -24,8 +21,47 @@ internal fun invalidNumberOfArgsError(errors: IErrorReporter, pos: Position, num
|
||||
errors.err("invalid number of arguments: expected $expected but got $numArgs", pos)
|
||||
}
|
||||
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// semantic analysis to see if the program is valid.
|
||||
internal fun Program.checkVarDeclsOnOwnLine(errors: IErrorReporter) {
|
||||
// A variable declaration directly followed by another statement on the same
|
||||
// source line (e.g. `ubyte length foo(42)`) parses as two statements, but it is
|
||||
// almost certainly a missing '=' or newline and very confusing, so report it.
|
||||
// This must run on the freshly parsed tree: later passes split off initializer
|
||||
// assignments and remove unused variables, which destroys the original adjacency.
|
||||
fun checkStatements(statements: List<Statement>) {
|
||||
for (i in 0 until statements.size - 1) {
|
||||
val cur = statements[i] as? VarDecl ?: continue
|
||||
if (cur.origin != VarDeclOrigin.USERCODE) continue
|
||||
val next = statements[i + 1]
|
||||
val curPos = cur.position
|
||||
val nextPos = next.position
|
||||
if (curPos.file == nextPos.file && curPos.line != 0 && curPos.line == nextPos.line && nextPos.startCol > curPos.endCol) {
|
||||
errors.err("variable declaration must be on its own line (did you forget '=' or a newline?)", nextPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val checker = object : IAstVisitor {
|
||||
override fun visit(block: Block) {
|
||||
checkStatements(block.statements)
|
||||
super.visit(block)
|
||||
}
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
checkStatements(subroutine.statements)
|
||||
super.visit(subroutine)
|
||||
}
|
||||
override fun visit(scope: AnonymousScope) {
|
||||
checkStatements(scope.statements)
|
||||
super.visit(scope)
|
||||
}
|
||||
override fun visit(decl: VarDecl) {
|
||||
// Do not traverse into the declaration: its value can still be an
|
||||
// ExpressionTuple at this stage, which cannot be visited.
|
||||
}
|
||||
}
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) { // semantic analysis to see if the program is valid.
|
||||
val parentChecker = ParentNodeChecker()
|
||||
parentChecker.visit(this)
|
||||
val checker = AstChecker(this, errors, compilerOptions)
|
||||
|
||||
@@ -11,6 +11,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.parser.Prog8Keywords
|
||||
|
||||
/**
|
||||
* This checks for naming conflicts, not for correct symbol references yet.
|
||||
@@ -30,15 +31,21 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null) {
|
||||
if (block.isInLibrary)
|
||||
nameError(existing.name, existing.position, block)
|
||||
if(block.name in BuiltinFunctions)
|
||||
errors.err("builtin function cannot be redefined", block.position)
|
||||
else if(block.name in keywords)
|
||||
errors.err("block name cannot be a keyword", block.position)
|
||||
else {
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null) {
|
||||
if (block.isInLibrary)
|
||||
nameError(existing.name, existing.position, block)
|
||||
else
|
||||
nameError(block.name, block.position, existing)
|
||||
}
|
||||
else
|
||||
nameError(block.name, block.position, existing)
|
||||
blocks[block.name] = block
|
||||
}
|
||||
else
|
||||
blocks[block.name] = block
|
||||
|
||||
super.visit(block)
|
||||
}
|
||||
@@ -79,41 +86,8 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
private val keywords = setOf(
|
||||
"void",
|
||||
"on",
|
||||
"in",
|
||||
"to",
|
||||
"if",
|
||||
"downto",
|
||||
"else",
|
||||
"step",
|
||||
"call",
|
||||
"goto",
|
||||
"inline",
|
||||
"struct",
|
||||
"then",
|
||||
"alias",
|
||||
"defer",
|
||||
"const",
|
||||
"sizeof",
|
||||
"as",
|
||||
"return",
|
||||
"break",
|
||||
"continue",
|
||||
"true",
|
||||
"false",
|
||||
"sub",
|
||||
"asmsub",
|
||||
"extsub",
|
||||
"clobbers",
|
||||
"while",
|
||||
"do",
|
||||
"until",
|
||||
"repeat",
|
||||
"unroll",
|
||||
"when"
|
||||
)
|
||||
// the set of reserved keywords in the prog8 language
|
||||
private val keywords: Set<String> by lazy { Prog8Keywords.reservedWords }
|
||||
|
||||
override fun visit(struct: StructDecl) {
|
||||
if(struct.name in BuiltinFunctions)
|
||||
@@ -121,7 +95,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
else if(struct.name in keywords)
|
||||
errors.err("struct name cannot be a keyword", struct.position)
|
||||
|
||||
|
||||
super.visit(struct)
|
||||
}
|
||||
|
||||
|
||||
@@ -151,17 +151,47 @@ class AstPreprocessor(val program: Program,
|
||||
if(decl.names.size>1) {
|
||||
// we need to handle multi-decl here too, the desugarer maybe has not processed it here yet...
|
||||
if(decl.value!=null) {
|
||||
decl.names.forEach { name ->
|
||||
val target = AssignTarget(
|
||||
IdentifierReference(listOf(name), decl.position),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
position = decl.position
|
||||
// only use a multi-value assignment if the initializer is a function call with multiple
|
||||
// return values, so that the call is only performed once. Otherwise (e.g. a constant
|
||||
// initializer) we have to fall back to individual assignments.
|
||||
val fcall = decl.value as? IFunctionCall
|
||||
val fcallTarget = fcall?.target?.targetSubroutine()
|
||||
val isBuiltinMultiReturn = fcall?.target?.let { target ->
|
||||
val name = target.nameInSource.singleOrNull()
|
||||
name != null && name in program.builtinFunctions.names &&
|
||||
program.builtinFunctions.returnTypes(name).size > 1
|
||||
} ?: false
|
||||
if(fcallTarget!=null || isBuiltinMultiReturn) {
|
||||
val variables = decl.names.map { name ->
|
||||
AssignTarget(
|
||||
IdentifierReference(listOf(name), decl.position),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
position = decl.position
|
||||
)
|
||||
}
|
||||
val assign = Assignment(
|
||||
AssignTarget(null, null, null, variables, false, position = decl.position),
|
||||
decl.value!!,
|
||||
AssignmentOrigin.VARINIT,
|
||||
decl.position
|
||||
)
|
||||
val assign = Assignment(target.copy(), decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(AstInsert.after(decl, assign, scope))
|
||||
} else {
|
||||
decl.names.forEach { name ->
|
||||
val target = AssignTarget(
|
||||
IdentifierReference(listOf(name), decl.position),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
position = decl.position
|
||||
)
|
||||
val assign = Assignment(target, decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(AstInsert.after(decl, assign, scope))
|
||||
}
|
||||
}
|
||||
replacements.add(AstRemove(decl, scope))
|
||||
decl.value = null
|
||||
@@ -390,10 +420,16 @@ class AstPreprocessor(val program: Program,
|
||||
val tgt = alias.target.targetStatement(program.builtinFunctions)
|
||||
|
||||
if(tgt==null) {
|
||||
// The target may refer to an enum member that hasn't been desugared into consts yet.
|
||||
// Defer the error in that case; a later iteration will resolve it after desugaring.
|
||||
if(refersToNotYetDesugaredEnumMember(alias))
|
||||
return noModifications
|
||||
errors.undefined(alias.target.nameInSource, position = alias.target.position)
|
||||
} else {
|
||||
if(alias.alias == alias.target.nameInSource.first()) {
|
||||
errors.err("alias loop", alias.position)
|
||||
} else if(tgt is Enumeration) {
|
||||
errors.err("cannot alias an enum '${alias.target.nameInSource.joinToString(".")}', alias individual members instead (e.g. ${alias.target.nameInSource.joinToString(".")}::Member)", alias.position)
|
||||
} else if(tgt is Alias) {
|
||||
var chainedAlias = alias
|
||||
var chainedTargetName = alias.target
|
||||
@@ -433,6 +469,23 @@ class AstPreprocessor(val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun refersToNotYetDesugaredEnumMember(alias: Alias): Boolean {
|
||||
val nameInSource = alias.target.nameInSource
|
||||
if(nameInSource.isEmpty())
|
||||
return false
|
||||
val last = nameInSource.last()
|
||||
if("::" !in last)
|
||||
return false
|
||||
val parts = last.split("::", limit = 2)
|
||||
if(parts.size != 2 || parts[0].isEmpty() || parts[1].isEmpty())
|
||||
return false
|
||||
val enumName = parts[0]
|
||||
val memberName = parts[1]
|
||||
val enumPath = nameInSource.dropLast(1) + enumName
|
||||
val enumNode = alias.definingScope.lookup(enumPath) as? Enumeration ?: return false
|
||||
return enumNode.members.any { it.first == memberName }
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<AstModification> {
|
||||
// convert all antlr names to structs
|
||||
if(typecast.type.subTypeFromAntlr!=null) {
|
||||
|
||||
@@ -589,7 +589,13 @@ _after:
|
||||
|
||||
if(expr.operator==".") {
|
||||
if(expr.left is IdentifierReference) {
|
||||
require(expr.right !is IdentifierReference)
|
||||
if(expr.right is IdentifierReference) {
|
||||
// (a.b).c -> a.b.c - merge dotted names (handles parenthesized pointer chains like (t.next).flag)
|
||||
val leftIdent = expr.left as IdentifierReference
|
||||
val rightIdent = expr.right as IdentifierReference
|
||||
val combined = IdentifierReference(leftIdent.nameInSource + rightIdent.nameInSource, expr.position)
|
||||
return listOf(AstReplaceNode(expr, combined, parent))
|
||||
}
|
||||
val ri = expr.right as? ArrayIndexedExpression
|
||||
if(ri!=null && ri.plainarrayvar!=null) {
|
||||
val leftIdent = expr.left as IdentifierReference
|
||||
@@ -648,6 +654,52 @@ _after:
|
||||
val ident = IdentifierReference(right.chain, right.position)
|
||||
return listOf(AstReplaceNode(expr.right, ident, expr))
|
||||
}
|
||||
|
||||
// Generic pointer field access on complex expressions, e.g. (expr as ^^Struct).field
|
||||
// Simple identifier/array left-hand-sides (a.b.c, ptr[i].field) are handled elsewhere.
|
||||
// Writes through such an expression can't occur: the grammar only allows them as read expressions.
|
||||
if(expr.left !is IdentifierReference && expr.left !is ArrayIndexedExpression) {
|
||||
val fieldIdent = expr.right as? IdentifierReference
|
||||
if(fieldIdent!=null) {
|
||||
val leftDt = expr.left.inferType(program).getOrUndef()
|
||||
val struct = if(leftDt.isPointer) leftDt.subType as? StructDecl else null
|
||||
val fieldName = fieldIdent.nameInSource.first()
|
||||
val fieldDt = struct?.getFieldType(fieldName)
|
||||
if(struct!=null && fieldDt!=null) {
|
||||
// (ptr).field --> peekXXX(ptr as uword + offsetof(Struct.field))
|
||||
val offset = struct.offsetof(fieldName, program.memsizer)!!.toInt()
|
||||
val ptrAsUword = TypecastExpression(expr.left.copy(), DataType.UWORD, true, expr.left.position)
|
||||
val address: Expression = if(offset==0) ptrAsUword
|
||||
else BinaryExpression(ptrAsUword, "+", NumericLiteral.optimalInteger(offset, expr.position), expr.position)
|
||||
fun peekCall(func: String) =
|
||||
FunctionCallExpression(IdentifierReference(listOf(func), expr.position), mutableListOf(address), expr.position)
|
||||
val readExpr: Expression = when {
|
||||
fieldDt.isBool -> peekCall("peekbool")
|
||||
fieldDt.isUnsignedByte -> DirectMemoryRead(address, expr.position)
|
||||
fieldDt.isSignedByte -> TypecastExpression(DirectMemoryRead(address, expr.position), DataType.BYTE, true, expr.position)
|
||||
fieldDt.isUnsignedWord -> peekCall("peekw")
|
||||
fieldDt.isSignedWord -> TypecastExpression(peekCall("peekw"), DataType.WORD, true, expr.position)
|
||||
fieldDt.isLong -> peekCall("peekl")
|
||||
fieldDt.isFloat -> peekCall("peekf")
|
||||
fieldDt.isPointer -> TypecastExpression(peekCall("peekw"), fieldDt, true, expr.position)
|
||||
else -> {
|
||||
errors.err("unsupported field type for pointer dereference", expr.position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
if(fieldIdent.nameInSource.size==1)
|
||||
return listOf(AstReplaceNode(expr, readExpr, parent))
|
||||
// (ptr).field.rest... --> (read of first field) . rest... (will be desugared further in the next pass)
|
||||
if(!fieldDt.isPointer) {
|
||||
errors.err("cannot dereference non-pointer field '$fieldName'", fieldIdent.position)
|
||||
return noModifications
|
||||
}
|
||||
val rest = IdentifierReference(fieldIdent.nameInSource.drop(1), fieldIdent.position)
|
||||
val chained = BinaryExpression(readExpr, ".", rest, expr.position)
|
||||
return listOf(AstReplaceNode(expr, chained, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
|
||||
@@ -528,9 +528,9 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
private fun transform(initializer: StaticStructInitializer): PtFunctionCall {
|
||||
val targetStruct = initializer.structname.targetStructDecl()!!
|
||||
val call = PtFunctionCall("prog8_lib_structalloc", true, true, arrayOf(DataType.pointer(targetStruct)), initializer.position)
|
||||
|
||||
|
||||
val flattenedArgs = initializer.args.flattenArgs()
|
||||
val expectedTotalElements = targetStruct.fields.sumOf { it.arraySize ?: 1 }
|
||||
val expectedTotalElements = targetStruct.fields.sumOf { it.constSize() ?: 1 }
|
||||
if (flattenedArgs.isNotEmpty() && flattenedArgs.size != expectedTotalElements) {
|
||||
errors.err("invalid number of field values: expected $expectedTotalElements flattened elements for struct '${targetStruct.name}' but got ${flattenedArgs.size}", initializer.position)
|
||||
}
|
||||
@@ -817,7 +817,12 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
}
|
||||
|
||||
private fun transform(struct: StructDecl): PtStructDecl {
|
||||
return PtStructDecl(struct.name, struct.fields.map { PtStructField(it.type, it.name, it.arraySize) }, struct.position)
|
||||
return PtStructDecl(struct.name, struct.fields.map {
|
||||
val size = it.constSize()
|
||||
if(it.isArray && size==null)
|
||||
errors.err("array length must be known at compile-time", it.arraySize?.position ?: struct.position)
|
||||
PtStructField(it.type, it.name, size)
|
||||
}, struct.position)
|
||||
}
|
||||
|
||||
private fun transform(srcWhen: When): PtWhen {
|
||||
|
||||
@@ -152,7 +152,7 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
ExampleSizes("bdmusic-irq", c64SizeOptimized=1001, c64SizeUnoptimized=1019),
|
||||
ExampleSizes("bdmusic", c64SizeOptimized=938, c64SizeUnoptimized=1011),
|
||||
ExampleSizes("charset", c64SizeOptimized=835, c64SizeUnoptimized=860),
|
||||
ExampleSizes("cube3d-sprites", c64SizeOptimized=2664, c64SizeUnoptimized=2769),
|
||||
ExampleSizes("cube3d-sprites", c64SizeOptimized=2664, c64SizeUnoptimized=2705),
|
||||
ExampleSizes("fileselector", c64SizeOptimized=3523, c64SizeUnoptimized=4510),
|
||||
ExampleSizes("library/main", c64SizeOptimized=321, c64SizeUnoptimized=1147),
|
||||
ExampleSizes("multiplexer", c64SizeOptimized=1356, c64SizeUnoptimized=1507),
|
||||
@@ -162,7 +162,7 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
ExampleSizes("simplemultiplexer", c64SizeOptimized=843, c64SizeUnoptimized=900),
|
||||
ExampleSizes("sprites", c64SizeOptimized=963, c64SizeUnoptimized=1028),
|
||||
ExampleSizes("starfield", c64SizeOptimized=743, c64SizeUnoptimized=758),
|
||||
ExampleSizes("turtle-gfx", c64SizeOptimized=2701, c64SizeUnoptimized=2806),
|
||||
ExampleSizes("turtle-gfx", c64SizeOptimized=2701, c64SizeUnoptimized=2812),
|
||||
ExampleSizes("wizzine", c64SizeOptimized=1124, c64SizeUnoptimized=1224),
|
||||
// boingball, cube3d, tehtriz are in BothC64andCx16
|
||||
),
|
||||
@@ -211,7 +211,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
ExampleSizes("fileselector/main", cx16SizeOptimized=480, cx16SizeUnoptimized=2314),
|
||||
// fileselector/fselector and fileselector/namesorting don't compile (empty source files)
|
||||
ExampleSizes("pointers/fountain-cx16", cx16SizeOptimized=1179, cx16SizeUnoptimized=1520),
|
||||
ExampleSizes("amiga", cx16SizeOptimized=7631, cx16SizeUnoptimized=11628),
|
||||
ExampleSizes("amiga", cx16SizeOptimized=7810, cx16SizeUnoptimized=11827),
|
||||
ExampleSizes("audioroutines", cx16SizeOptimized=948, cx16SizeUnoptimized=1244),
|
||||
ExampleSizes("automatons", cx16SizeOptimized=1037, cx16SizeUnoptimized=1342),
|
||||
ExampleSizes("balloonflight", cx16SizeOptimized=3187, cx16SizeUnoptimized=4202),
|
||||
@@ -220,8 +220,8 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
ExampleSizes("bubbleuniverse", cx16SizeOptimized=1367, cx16SizeUnoptimized=1883),
|
||||
ExampleSizes("charfade", cx16SizeOptimized=1054, cx16SizeUnoptimized=1376),
|
||||
ExampleSizes("charsets", cx16SizeOptimized=1125, cx16SizeUnoptimized=1417),
|
||||
ExampleSizes("circles", cx16SizeOptimized=2377, cx16SizeUnoptimized=4495),
|
||||
ExampleSizes("cobramk3-gfx", cx16SizeOptimized=7756, cx16SizeUnoptimized=10434),
|
||||
ExampleSizes("circles", cx16SizeOptimized=2535, cx16SizeUnoptimized=4689),
|
||||
ExampleSizes("cobramk3-gfx", cx16SizeOptimized=7642, cx16SizeUnoptimized=10368),
|
||||
ExampleSizes("colorbars", cx16SizeOptimized=1606, cx16SizeUnoptimized=1904),
|
||||
ExampleSizes("cxlogo", cx16SizeOptimized=599, cx16SizeUnoptimized=895),
|
||||
ExampleSizes("diskspeed", cx16SizeOptimized=6117, cx16SizeUnoptimized=8091),
|
||||
@@ -243,9 +243,9 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
ExampleSizes("sortingbench", cx16SizeOptimized=2928, cx16SizeUnoptimized=3857),
|
||||
ExampleSizes("spotlight", cx16SizeOptimized=908, cx16SizeUnoptimized=1364),
|
||||
ExampleSizes("starszoom", cx16SizeOptimized=2399, cx16SizeUnoptimized=4820),
|
||||
ExampleSizes("test_gfx_hires", cx16SizeOptimized=4760, cx16SizeUnoptimized=7444),
|
||||
ExampleSizes("test_gfx_lores", cx16SizeOptimized=5820, cx16SizeUnoptimized=7004),
|
||||
ExampleSizes("testmonogfx", cx16SizeOptimized=10255, cx16SizeUnoptimized=11631),
|
||||
ExampleSizes("test_gfx_hires", cx16SizeOptimized=4966, cx16SizeUnoptimized=7650),
|
||||
ExampleSizes("test_gfx_lores", cx16SizeOptimized=6019, cx16SizeUnoptimized=7182),
|
||||
ExampleSizes("testmonogfx", cx16SizeOptimized=10468, cx16SizeUnoptimized=11847),
|
||||
ExampleSizes("textspotlight", cx16SizeOptimized=3431, cx16SizeUnoptimized=3788),
|
||||
),
|
||||
listOf(false, true)
|
||||
@@ -276,9 +276,9 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
listOf(
|
||||
ExampleSizes("balls", c64SizeOptimized=885, c64SizeUnoptimized=927, cx16SizeOptimized=874, cx16SizeUnoptimized=1200),
|
||||
ExampleSizes("boingball", c64SizeOptimized=1487, c64SizeUnoptimized=1553, cx16SizeOptimized=1463, cx16SizeUnoptimized=1944),
|
||||
ExampleSizes("cube3d", c64SizeOptimized=2436, c64SizeUnoptimized=2513, cx16SizeOptimized=2373, cx16SizeUnoptimized=2704),
|
||||
ExampleSizes("cube3d-float", c64SizeOptimized=2591, c64SizeUnoptimized=2643, cx16SizeOptimized=2447, cx16SizeUnoptimized=2770),
|
||||
ExampleSizes("cube3d-gfx", c64SizeOptimized=3528, c64SizeUnoptimized=3625, cx16SizeOptimized=2198, cx16SizeUnoptimized=2522),
|
||||
ExampleSizes("cube3d", c64SizeOptimized=2360, c64SizeUnoptimized=2446, cx16SizeOptimized=2299, cx16SizeUnoptimized=2645),
|
||||
ExampleSizes("cube3d-float", c64SizeOptimized=2390, c64SizeUnoptimized=2441, cx16SizeOptimized=2283, cx16SizeUnoptimized=2606),
|
||||
ExampleSizes("cube3d-gfx", c64SizeOptimized=3468, c64SizeUnoptimized=3600, cx16SizeOptimized=2138, cx16SizeUnoptimized=2482),
|
||||
ExampleSizes("dirlist", c64SizeOptimized=1515, c64SizeUnoptimized=2291, cx16SizeOptimized=1525, cx16SizeUnoptimized=3270),
|
||||
ExampleSizes("fibonacci", c64SizeOptimized=582, c64SizeUnoptimized=601, cx16SizeOptimized=634, cx16SizeUnoptimized=933),
|
||||
ExampleSizes("fractal-tree", c64SizeOptimized=2778, c64SizeUnoptimized=2821, cx16SizeOptimized=1168, cx16SizeUnoptimized=1462),
|
||||
|
||||
@@ -8,13 +8,16 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeIn
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.string.shouldStartWith
|
||||
import prog8.ast.Program
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.PROG8_CONTAINER_MODULES
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.source.ImportFileSystem
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.compiler.ModuleImporter
|
||||
import prog8.parser.MultipleParseErrors
|
||||
import prog8.parser.Prog8Parser
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
@@ -190,6 +193,22 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
}
|
||||
|
||||
context("ImportFromResourceModule") {
|
||||
test("neighbor directory search is skipped for modules loaded from internal library resources") {
|
||||
val importer = makeImporter(null)
|
||||
val resourceModule = Prog8Parser.parseModule(ImportFileSystem.getResource("/prog8lib/cx16/textio.p8"))
|
||||
resourceModule.position.file shouldStartWith "library:"
|
||||
|
||||
val (result, searched) = importer.getModuleFromFile("syslib", resourceModule)
|
||||
withClue("syslib only exists as an internal library resource, not on the filesystem") {
|
||||
result.getErrorOrElse { error("expected a filesystem search failure") }.file.name shouldBe "syslib"
|
||||
}
|
||||
withClue("no filesystem search location may be derived from the library: origin: $searched") {
|
||||
searched.none { it.toString().contains("library:") } shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("ImportLibraryModule") {
|
||||
context("WithInvalidName") {
|
||||
test("testWithNonExistingName") {
|
||||
|
||||
@@ -2823,4 +2823,158 @@ main {
|
||||
result shouldNotBe null
|
||||
}
|
||||
}
|
||||
|
||||
test("parenthesized pointer field access desugars same as unparenthesized") {
|
||||
val src="""
|
||||
main {
|
||||
struct Thing {
|
||||
uword port
|
||||
^^Thing next
|
||||
bool flag
|
||||
}
|
||||
|
||||
sub start() {
|
||||
^^Thing @shared t
|
||||
bool @shared d1 = t.next.flag
|
||||
bool @shared d2 = (t.next).flag
|
||||
ubyte @shared d3 = t.next.flag as ubyte
|
||||
ubyte @shared d4 = (t.next).flag as ubyte
|
||||
bool @shared d5 = t.next.next.flag
|
||||
bool @shared d6 = (t.next).next.flag
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
|
||||
val assigns = result.compilerAst.entrypoint.statements.filterIsInstance<Assignment>()
|
||||
.associateBy { it.target.identifier!!.nameInSource.last() }
|
||||
|
||||
val d1 = assigns.getValue("d1").value as PtrDereference
|
||||
d1.chain shouldBe listOf("t", "next", "flag")
|
||||
(assigns.getValue("d2").value as PtrDereference).chain shouldBe d1.chain
|
||||
((assigns.getValue("d3").value as TypecastExpression).expression as PtrDereference).chain shouldBe d1.chain
|
||||
((assigns.getValue("d4").value as TypecastExpression).expression as PtrDereference).chain shouldBe d1.chain
|
||||
val d5 = assigns.getValue("d5").value as PtrDereference
|
||||
d5.chain shouldBe listOf("t", "next", "next", "flag")
|
||||
(assigns.getValue("d6").value as PtrDereference).chain shouldBe d5.chain
|
||||
|
||||
compileText(VMTarget(), true, src, outputDir) shouldNotBe null
|
||||
compileText(C64Target(), false, src, outputDir) shouldNotBe null
|
||||
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("field access on casted struct pointer expression") {
|
||||
val src="""
|
||||
main {
|
||||
struct Thing {
|
||||
uword port
|
||||
^^Thing next
|
||||
bool flag
|
||||
}
|
||||
|
||||
sub start() {
|
||||
^^Thing @shared t
|
||||
bool @shared d1 = (t.port as ^^Thing).flag
|
||||
ubyte @shared d2 = (t.port as ^^Thing).flag as ubyte
|
||||
^^Thing @shared d3 = (t.port as ^^Thing).next
|
||||
bool @shared d4 = (t.port as ^^Thing).next.flag
|
||||
bool @shared d5 = ((t.port as ^^Thing).next).flag
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = compileText(VMTarget(), false, src, outputDir, errors, writeAssembly = false)
|
||||
withClue(errors.errors.joinToString("\n")) {
|
||||
result shouldNotBe null
|
||||
}
|
||||
val assigns = result!!.compilerAst.entrypoint.statements.filterIsInstance<Assignment>()
|
||||
.associateBy { it.target.identifier!!.nameInSource.last() }
|
||||
|
||||
// (t.port as ^^Thing).field --> peekXXX((t.port as ^^Thing as uword) + offsetof(field))
|
||||
val d1 = assigns.getValue("d1").value as FunctionCallExpression
|
||||
d1.target.nameInSource shouldBe listOf("peekbool")
|
||||
val d1addr = d1.args.single() as BinaryExpression
|
||||
d1addr.operator shouldBe "+"
|
||||
(d1addr.right as NumericLiteral).number shouldBe 4.0 // offsetof(Thing.flag)
|
||||
|
||||
val d2 = assigns.getValue("d2").value as TypecastExpression
|
||||
d2.type shouldBe DataType.UBYTE
|
||||
(d2.expression as FunctionCallExpression).target.nameInSource shouldBe listOf("peekbool")
|
||||
|
||||
// pointer-typed field read: peekw + cast back to the pointer type
|
||||
val d3 = assigns.getValue("d3").value as TypecastExpression
|
||||
d3.type.isPointer shouldBe true
|
||||
val d3peek = d3.expression as FunctionCallExpression
|
||||
d3peek.target.nameInSource shouldBe listOf("peekw")
|
||||
((d3peek.args.single() as BinaryExpression).right as NumericLiteral).number shouldBe 2.0 // offsetof(Thing.next)
|
||||
|
||||
// chained: inner pointer-typed field is read via peekw first, then .flag via peekbool
|
||||
val d4 = assigns.getValue("d4").value as FunctionCallExpression
|
||||
d4.target.nameInSource shouldBe listOf("peekbool")
|
||||
val d4addr = d4.args.single() as BinaryExpression
|
||||
(d4addr.right as NumericLiteral).number shouldBe 4.0
|
||||
val d4inner = (d4addr.left as TypecastExpression).expression as TypecastExpression
|
||||
(d4inner.expression as FunctionCallExpression).target.nameInSource shouldBe listOf("peekw")
|
||||
|
||||
// explicit parenthesized chain desugars the same
|
||||
(assigns.getValue("d5").value as FunctionCallExpression).target.nameInSource shouldBe listOf("peekbool")
|
||||
|
||||
compileText(VMTarget(), true, src, outputDir) shouldNotBe null
|
||||
compileText(C64Target(), false, src, outputDir) shouldNotBe null
|
||||
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
xtest("function call returning pointer - field access chaining as assignment target [nasty parser problem]") {
|
||||
val src="""
|
||||
main {
|
||||
struct Thing {
|
||||
uword port
|
||||
^^Thing next
|
||||
bool flag
|
||||
}
|
||||
|
||||
sub getThing() -> ^^Thing {
|
||||
return 0
|
||||
}
|
||||
|
||||
sub start() {
|
||||
getThing().flag = true
|
||||
getThing().next.flag = true
|
||||
getThing().next.next.flag = false
|
||||
getThing().port = 1234
|
||||
getThing().next.port = 5678
|
||||
}
|
||||
}"""
|
||||
// currently fails at parse stage, should eventually compile for both VM and 6502 without needing assembly generation
|
||||
compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
compileText(Cx16Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("function call returning pointer - field access chaining as value (RHS) works") {
|
||||
val src="""
|
||||
main {
|
||||
struct Thing {
|
||||
uword port
|
||||
^^Thing next
|
||||
bool flag
|
||||
}
|
||||
|
||||
sub getThing() -> ^^Thing {
|
||||
return 0
|
||||
}
|
||||
|
||||
sub start() {
|
||||
bool a = getThing().flag
|
||||
bool b = getThing().next.flag
|
||||
bool c = getThing().next.next.flag
|
||||
uword d = getThing().port
|
||||
uword e = getThing().next.port
|
||||
bool f = getThing().flag and getThing().next.flag
|
||||
uword g = getThing().port + getThing().next.port
|
||||
if getThing().flag {
|
||||
a = true
|
||||
}
|
||||
}
|
||||
}"""
|
||||
// RHS value case already works for both VM and 6502 without needing assembly generation
|
||||
compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
compileText(Cx16Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -1063,4 +1063,30 @@ main {
|
||||
compileText(C64Target(), false, src, outputDir, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), false, src, outputDir, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("typecast of array element as left operand of shift into long target doesn't crash codegen") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
word[8] @shared rotatedx
|
||||
ubyte @shared p1 = 0
|
||||
long @shared x = rotatedx[p1] as long >> 7
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), true, text, outputDir, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), true, text, outputDir, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("typecast of array element as left operand of long addition into long target doesn't crash codegen") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
word[8] @shared rotatedz
|
||||
ubyte @shared p1 = 0
|
||||
long @shared x = (rotatedz[p1] as long + 43520) >> 7
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), true, text, outputDir, writeAssembly = true) shouldNotBe null
|
||||
compileText(VMTarget(), true, text, outputDir, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -694,4 +694,122 @@ main {
|
||||
compileText(VMTarget(), false, src, outputDir, errors) shouldBe null
|
||||
errors.printedErrors.any { it.contains("2D arrays are not allowed as struct fields") } shouldBe true
|
||||
}
|
||||
|
||||
test("struct field array size can be const expression") {
|
||||
val src = """
|
||||
main {
|
||||
const ubyte SIZE = 5
|
||||
struct Node {
|
||||
ubyte[SIZE] data
|
||||
uword top
|
||||
}
|
||||
sub start() {
|
||||
^^Node n = [ [1,2,3,4,5], 9999 ]
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors, false)
|
||||
errors.errors shouldBe emptyList()
|
||||
}
|
||||
|
||||
test("struct field array size can be folded constant expression") {
|
||||
val src = """
|
||||
main {
|
||||
const ubyte BASE = 3
|
||||
struct Node {
|
||||
ubyte[BASE*2] data
|
||||
}
|
||||
sub start() {
|
||||
^^Node n = [ [1,2,3,4,5,6] ]
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors, false)
|
||||
errors.errors shouldBe emptyList()
|
||||
}
|
||||
|
||||
test("struct field array size rejects non-constant expression") {
|
||||
val src = """
|
||||
main {
|
||||
uword dyn
|
||||
struct Node {
|
||||
uword[dyn] data
|
||||
}
|
||||
sub start() {
|
||||
dyn = 5
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors) shouldBe null
|
||||
errors.errors.any { it.contains("array length must be known at compile-time") } shouldBe true
|
||||
}
|
||||
|
||||
test("struct field array size rejects zero size") {
|
||||
val src = """
|
||||
main {
|
||||
const ubyte SIZE = 0
|
||||
struct Node {
|
||||
ubyte[SIZE] data
|
||||
}
|
||||
sub start() { }
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors) shouldBe null
|
||||
errors.errors.any { it.contains("array length must be positive") } shouldBe true
|
||||
}
|
||||
|
||||
test("variable declaration followed by statement on same line should give error") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte length foo(42)
|
||||
}
|
||||
sub foo(ubyte x) {
|
||||
x++
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "must be on its own line"
|
||||
}
|
||||
|
||||
test("variable declaration followed by statement on next line is fine") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte length
|
||||
length = 42
|
||||
foo(length)
|
||||
}
|
||||
sub foo(ubyte x) {
|
||||
x++
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors, false) shouldNotBe null
|
||||
errors.errors.size shouldBe 0
|
||||
}
|
||||
|
||||
test("two statements on same line is fine if first one is not a variable declaration") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
foo(1) foo(2)
|
||||
}
|
||||
sub foo(ubyte x) {
|
||||
cx16.r0 = x
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors, false) shouldNotBe null
|
||||
errors.errors.size shouldBe 0
|
||||
}
|
||||
})
|
||||
@@ -664,6 +664,61 @@ main {
|
||||
}
|
||||
|
||||
|
||||
test("alias to enum member works") {
|
||||
val src = """
|
||||
main {
|
||||
enum Priority { LOW, NORMAL, HIGH, EXTREME }
|
||||
|
||||
alias myNormal = Priority::NORMAL
|
||||
alias chainedNormal = myNormal
|
||||
|
||||
sub start() {
|
||||
ubyte @shared value1 = Priority::NORMAL
|
||||
ubyte @shared value2 = myNormal
|
||||
ubyte @shared value3 = chainedNormal
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)!!
|
||||
val start = result.compilerAst.allBlocks.first { it.name == "main" }.statements.filterIsInstance<prog8.ast.statements.Subroutine>().first { it.name == "start" }
|
||||
val assignments = start.statements.filterIsInstance<Assignment>()
|
||||
val value1 = (assignments[0].value as? NumericLiteral)?.number
|
||||
val value2 = (assignments[1].value as? NumericLiteral)?.number
|
||||
val value3 = (assignments[2].value as? NumericLiteral)?.number
|
||||
value1 shouldBe 1.0
|
||||
value2 shouldBe 1.0
|
||||
value3 shouldBe 1.0
|
||||
}
|
||||
|
||||
test("alias to whole enum gives error") {
|
||||
val src = """
|
||||
main {
|
||||
enum Priority { LOW, NORMAL, HIGH, EXTREME }
|
||||
alias myEnum = Priority
|
||||
|
||||
sub start() {
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "cannot alias an enum"
|
||||
}
|
||||
|
||||
test("alias to nonexistent enum member gives undefined error") {
|
||||
val src = """
|
||||
main {
|
||||
enum Priority { LOW, NORMAL, HIGH, EXTREME }
|
||||
alias myBad = Priority::NONEXISTENT
|
||||
|
||||
sub start() {
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "undefined symbol: Priority::NONEXISTENT"
|
||||
}
|
||||
|
||||
test("double colon in name only allowed in desugared enum members 2") {
|
||||
val src = """
|
||||
main {
|
||||
@@ -875,5 +930,98 @@ main {
|
||||
initValue.type shouldBe BaseDataType.LONG
|
||||
initValue.number.toInt() shouldBe 0x00200204
|
||||
}
|
||||
|
||||
test("mixed type bitwise operations promote to widest type") {
|
||||
// Regression test: ubyte | long was truncated to ubyte instead of promoting to long.
|
||||
// This caused MEMF_CHIP (ubyte) | MEMF_CLEAR (long) to lose the MEMF_CLEAR flag.
|
||||
val src = $$"""
|
||||
main {
|
||||
sub start() {
|
||||
const ubyte SMALL = $02
|
||||
const long BIG = $00010000
|
||||
long @shared result_or = SMALL | BIG
|
||||
long @shared result_and = (SMALL | BIG) & $00010002
|
||||
long @shared result_xor = SMALL ^ BIG
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false, errors = ErrorReporterForTests())!!
|
||||
val st = result.compilerAst.entrypoint.statements
|
||||
|
||||
val orAssign = st.filterIsInstance<Assignment>().find {
|
||||
(it.target.toExpression() as? IdentifierReference)?.nameInSource?.lastOrNull() == "result_or"
|
||||
}
|
||||
orAssign shouldNotBe null
|
||||
val orValue = orAssign!!.value as NumericLiteral
|
||||
orValue.type shouldBe BaseDataType.LONG
|
||||
orValue.number.toInt() shouldBe 0x00010002
|
||||
|
||||
val andAssign = st.filterIsInstance<Assignment>().find {
|
||||
(it.target.toExpression() as? IdentifierReference)?.nameInSource?.lastOrNull() == "result_and"
|
||||
}
|
||||
andAssign shouldNotBe null
|
||||
val andValue = andAssign!!.value as NumericLiteral
|
||||
andValue.type shouldBe BaseDataType.LONG
|
||||
andValue.number.toInt() shouldBe 0x00010002
|
||||
|
||||
val xorAssign = st.filterIsInstance<Assignment>().find {
|
||||
(it.target.toExpression() as? IdentifierReference)?.nameInSource?.lastOrNull() == "result_xor"
|
||||
}
|
||||
xorAssign shouldNotBe null
|
||||
val xorValue = xorAssign!!.value as NumericLiteral
|
||||
xorValue.type shouldBe BaseDataType.LONG
|
||||
xorValue.number.toInt() shouldBe 0x00010002
|
||||
}
|
||||
|
||||
// TODO this test should be enabled again once new-codegen branch is merged
|
||||
xtest("untyped const integer defaults to long") {
|
||||
val src = """
|
||||
main {
|
||||
const A = 42
|
||||
sub start() {}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
|
||||
val st = result.compilerAst.allBlocks.first { it.name=="main" }.statements
|
||||
val decl = st[0] as VarDecl
|
||||
decl.datatype.base shouldBe BaseDataType.LONG
|
||||
(decl.value as NumericLiteral).number shouldBe 42.0
|
||||
}
|
||||
|
||||
// TODO this test should be enabled again once new-codegen branch is merged
|
||||
xtest("untyped const float infers float type") {
|
||||
val src = """
|
||||
main {
|
||||
const PI = 3.14
|
||||
sub start() {}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
|
||||
val st = result.compilerAst.allBlocks.first { it.name=="main" }.statements
|
||||
val decl = st[0] as VarDecl
|
||||
decl.datatype.base shouldBe BaseDataType.FLOAT
|
||||
(decl.value as NumericLiteral).number shouldBe 3.14
|
||||
}
|
||||
|
||||
// TODO this test should be enabled again once new-codegen branch is merged
|
||||
xtest("untyped const bool infers bool type") {
|
||||
val src = """
|
||||
main {
|
||||
const FLAG = true
|
||||
sub start() {}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
|
||||
val st = result.compilerAst.allBlocks.first { it.name=="main" }.statements
|
||||
val decl = st[0] as VarDecl
|
||||
decl.datatype.base shouldBe BaseDataType.BOOL
|
||||
(decl.value as NumericLiteral).number shouldBe 1.0
|
||||
}
|
||||
|
||||
// TODO this test should be enabled again once new-codegen branch is merged
|
||||
xtest("untyped const memory() infers pointer type") {
|
||||
val src = """
|
||||
main {
|
||||
const M = memory("screen", 1000, 0)
|
||||
sub start() {}
|
||||
}"""
|
||||
compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1399,6 +1399,39 @@ main {
|
||||
errors.errors[3] shouldContain "builtin function cannot be redefined"
|
||||
}
|
||||
|
||||
test("block names cannot be builtin functions or keywords") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
}
|
||||
|
||||
private {
|
||||
sub foo() -> uword {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
memory {
|
||||
sub foo() -> uword {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
mkword {
|
||||
sub foo() -> uword {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), optimize=false, src, outputDir, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 3
|
||||
errors.errors[0] shouldContain "name cannot be a keyword"
|
||||
errors.errors[1] shouldContain "builtin function cannot be redefined"
|
||||
errors.errors[2] shouldContain "builtin function cannot be redefined"
|
||||
}
|
||||
|
||||
test("string and array multiplication require integer multiplicand") {
|
||||
val src="""
|
||||
main {
|
||||
|
||||
@@ -62,6 +62,116 @@ class TestExecution6502 : FunSpec({
|
||||
machine.assertMemory(0x02, 42)
|
||||
}
|
||||
|
||||
test("indexed pointer fields in structs") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
struct Node {
|
||||
bool flag
|
||||
^^uword uptr
|
||||
}
|
||||
struct Wrapper {
|
||||
bool z
|
||||
^^Node node
|
||||
}
|
||||
^^Node @shared n = [true, 4000]
|
||||
^^Node @shared inner = [true, 4500]
|
||||
^^Wrapper @shared w = [false, 4000]
|
||||
&ubyte poweroff = $f203
|
||||
&uword slot_low = $0300
|
||||
&uword slot_hi = $0302
|
||||
uword index
|
||||
|
||||
sub start() {
|
||||
n.uptr[3] = 9999
|
||||
n.uptr[1] = 7777
|
||||
n.uptr[9] = 5555
|
||||
slot_low = n.uptr[3]
|
||||
index = 1
|
||||
n.uptr[index] = 1111
|
||||
slot_hi = n.uptr[1]
|
||||
w.node = inner
|
||||
w.node.uptr[2] = 1234
|
||||
poweroff = 1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
val machine = compileResult!!.simulate()
|
||||
machine.assertMemory(0x0300, 9999 and 0xff)
|
||||
machine.assertMemory(0x0301, 9999 shr 8)
|
||||
machine.assertMemory(0x0302, 1111 and 0xff)
|
||||
machine.assertMemory(0x0303, 1111 shr 8)
|
||||
machine.assertMemory(4000+6, 9999 and 0xff)
|
||||
machine.assertMemory(4000+7, 9999 shr 8)
|
||||
machine.assertMemory(4000+2, 1111 and 0xff)
|
||||
machine.assertMemory(4000+3, 1111 shr 8)
|
||||
machine.assertMemory(4000+18, 5555 and 0xff)
|
||||
machine.assertMemory(4000+19, 5555 shr 8)
|
||||
machine.assertMemory(4500+4, 1234 and 0xff)
|
||||
machine.assertMemory(4500+5, 1234 shr 8)
|
||||
}
|
||||
|
||||
test("more struct pointer field operations") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
struct Node {
|
||||
bool flag
|
||||
^^uword uptr
|
||||
}
|
||||
^^Node @shared n = [true, 4000]
|
||||
^^Node @shared m = [true, 5000]
|
||||
^^uword @shared normalptr = 4000
|
||||
&ubyte poweroff = $f203
|
||||
&uword n_after_swap = $0300
|
||||
&uword m_after_swap = $0302
|
||||
&uword n_after_add = $0304
|
||||
&uword lsb_result = $0306
|
||||
&uword msb_result = $0308
|
||||
&uword idxinc_result = $030A
|
||||
|
||||
sub start() {
|
||||
swap(n.uptr, m.uptr)
|
||||
n_after_swap = n.uptr
|
||||
m_after_swap = m.uptr
|
||||
n.uptr += 2
|
||||
n_after_add = n.uptr
|
||||
n.uptr[3] = 100
|
||||
n.uptr[3]++
|
||||
idxinc_result = n.uptr[3]
|
||||
lsb_result = lsb(normalptr)
|
||||
msb_result = msb(normalptr)
|
||||
poweroff = 1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
val machine = compileResult!!.simulate()
|
||||
machine.assertMemory(0x0300, 5000 and 0xff)
|
||||
machine.assertMemory(0x0301, 5000 shr 8)
|
||||
machine.assertMemory(0x0302, 4000 and 0xff)
|
||||
machine.assertMemory(0x0303, 4000 shr 8)
|
||||
machine.assertMemory(0x0304, 5004 and 0xff)
|
||||
machine.assertMemory(0x0305, 5004 shr 8)
|
||||
machine.assertMemory(0x0306, 160)
|
||||
machine.assertMemory(0x0308, 15)
|
||||
machine.assertMemory(0x030A, 101)
|
||||
machine.assertMemory(0x030B, 0)
|
||||
machine.assertMemory(5010, 101)
|
||||
machine.assertMemory(5011, 0)
|
||||
}
|
||||
|
||||
test("serial output") {
|
||||
val src = $$"""
|
||||
%encoding iso
|
||||
@@ -379,6 +489,76 @@ class TestExecution6502 : FunSpec({
|
||||
machine.assertMemory(0x2003, 0x00)
|
||||
}
|
||||
|
||||
test("double increment/decrement of uword struct field via typed pointer") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
&ubyte poweroff = $f203
|
||||
&uword result = $02
|
||||
|
||||
struct Sprite {
|
||||
ubyte pad
|
||||
ubyte pad2
|
||||
uword x
|
||||
}
|
||||
|
||||
^^Sprite[4] sprites
|
||||
|
||||
sub start() {
|
||||
^^Sprite sprite = sprites[0]
|
||||
|
||||
sprite.x = 255
|
||||
sprite.x++
|
||||
sprite.x++
|
||||
result = sprite.x ; expect $0101
|
||||
poweroff = 1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
var machine = compileResult!!.simulate()
|
||||
machine.assertMemory(0x02, 0x01)
|
||||
machine.assertMemory(0x03, 0x01)
|
||||
|
||||
val src2 = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
&ubyte poweroff = $f203
|
||||
&uword result = $02
|
||||
|
||||
struct Sprite {
|
||||
ubyte pad
|
||||
ubyte pad2
|
||||
uword x
|
||||
}
|
||||
|
||||
^^Sprite[4] sprites
|
||||
|
||||
sub start() {
|
||||
^^Sprite sprite = sprites[0]
|
||||
|
||||
sprite.x = 1
|
||||
sprite.x--
|
||||
sprite.x--
|
||||
result = sprite.x ; expect $ffff
|
||||
poweroff = 1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult2 = compileText(Cx16Target(), false, src2, outputDir)
|
||||
machine = compileResult2!!.simulate()
|
||||
machine.assertMemory(0x02, 0xff)
|
||||
machine.assertMemory(0x03, 0xff)
|
||||
}
|
||||
|
||||
test("struct with inlined array memory layout (6502)") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
|
||||
@@ -643,6 +643,54 @@ main {
|
||||
lines.any { it.startsWith("jsr") && it.contains("p8s_test") } shouldBe true
|
||||
}
|
||||
|
||||
test("inline asm pipe operator is preserved as bitwise OR") {
|
||||
val src="""
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
main {
|
||||
sub start() {
|
||||
%asm {{
|
||||
lda #%00010000 | $1234>>16
|
||||
}}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, src, outputDir, writeAssembly = true)!!
|
||||
val asm = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm").readText()
|
||||
val lines = asm.lines().map { it.trim() }
|
||||
// the '|' must remain on the same line as the lda, not be split into a second line
|
||||
lines.any { it == "lda #%00010000 | $1234>>16" } shouldBe true
|
||||
lines.any { it == "$1234>>16" } shouldBe false
|
||||
}
|
||||
|
||||
test("subroutine with register-mapped parameter is not inlined") {
|
||||
val src= $$"""
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
main {
|
||||
sub start() {
|
||||
PlayVideo_STD(mkword($10, $20))
|
||||
}
|
||||
|
||||
sub PlayVideo_STD(uword params @R0) {
|
||||
TrampolineCallSTD($a000)
|
||||
}
|
||||
|
||||
asmsub TrampolineCallSTD(uword addr @AY) clobbers(A, Y) {
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), false, src, outputDir, writeAssembly = true)!!
|
||||
val asm = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm").readText()
|
||||
val lines = asm.lines().map { it.trim() }
|
||||
// the R0 argument setup must be preserved because the inlined body doesn't
|
||||
// explicitly reference the register-mapped parameter
|
||||
lines.any { it == "sta cx16.r0" } shouldBe true
|
||||
lines.any { it == "sty cx16.r0+1" } shouldBe true
|
||||
lines.any { it.startsWith("jsr") && it.contains("p8s_PlayVideo_STD") } shouldBe true
|
||||
}
|
||||
|
||||
test("if not without else is not swapped") {
|
||||
val src="""
|
||||
main {
|
||||
|
||||
@@ -194,8 +194,13 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
output("private ")
|
||||
outputln("struct ${struct.name} {")
|
||||
for(member in struct.fields) {
|
||||
val arraySuffix = if(member.isArray) "[${member.arraySize}]" else ""
|
||||
outputlni( " ${member.type}$arraySuffix ${member.name}")
|
||||
outputi(" ${member.type}")
|
||||
if(member.isArray) {
|
||||
output("[")
|
||||
member.arraySize?.indexExpr?.accept(this)
|
||||
output("]")
|
||||
}
|
||||
outputln(" ${member.name}")
|
||||
}
|
||||
outputlni("}")
|
||||
}
|
||||
|
||||
@@ -814,11 +814,11 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
|
||||
override fun visitStructdeclaration(ctx: StructdeclarationContext): StructDecl {
|
||||
val name = getname(ctx.identifier())
|
||||
val fieldDefs = ctx.structfielddecl().map { getStructField(it) }
|
||||
val flattened = fieldDefs.flatMap { (dt, names, arrSize) -> names.map { StructField(dt, it, arrSize) }}
|
||||
val flattened = fieldDefs.flatMap { (dt, names, arrSize) -> names.map { StructField(dt, it, arrSize?.copy()) }}
|
||||
return StructDecl(name, flattened.toTypedArray(), ctx.PRIVATE() != null, ctx.toPosition())
|
||||
}
|
||||
|
||||
private data class StructFieldDef(val type: DataType, val names: List<String>, val arraySize: Int?)
|
||||
private data class StructFieldDef(val type: DataType, val names: List<String>, val arraySize: ArrayIndex?)
|
||||
|
||||
private fun getStructField(ctx: StructfielddeclContext): StructFieldDef {
|
||||
val identifiers = ctx.identifierlist()?.identifier() ?: emptyList()
|
||||
@@ -833,9 +833,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
|
||||
throw SyntaxError("2D arrays are not allowed as struct fields", ctx.toPosition())
|
||||
val dt = baseDt.elementToArray()
|
||||
val arrayIndexExpr = arrayIndices[0].accept(this) as ArrayIndex
|
||||
val size = (arrayIndexExpr.indexExpr as? NumericLiteral)?.number?.toInt()
|
||||
?: throw SyntaxError("array field size must be a constant integer expression", ctx.toPosition())
|
||||
dt to size
|
||||
dt to arrayIndexExpr
|
||||
} else {
|
||||
baseDt to null
|
||||
}
|
||||
|
||||
@@ -276,7 +276,11 @@ class BinaryExpression(
|
||||
// SOMEEXPRESSION . NAME
|
||||
val leftDt = leftExpr.inferType(program)
|
||||
if (leftDt.isPointer) leftDt.getOrUndef().subType else null
|
||||
} else null
|
||||
} else {
|
||||
// generic pointer expression . field (e.g. (expr as ^^Struct).field, or (ptrNext).field)
|
||||
val leftDt = left.inferType(program)
|
||||
if (leftDt.isPointer) leftDt.getOrUndef().subType else null
|
||||
}
|
||||
if (struct == null)
|
||||
InferredTypes.unknown()
|
||||
else {
|
||||
|
||||
@@ -517,20 +517,37 @@ class StructDecl(override val name: String, val fields: Array<StructField>, val
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
fields.forEach { it.arraySize?.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = false
|
||||
override fun copy() = StructDecl(name, fields.clone(), isPrivate, position)
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is ArrayIndex)
|
||||
val field = fields.firstOrNull { it.arraySize === node }
|
||||
?: throw FatalAstException("invalid replace in $this at ${node.position}: $node -> $replacement")
|
||||
field.arraySize = replacement
|
||||
replacement.linkParents(this)
|
||||
}
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = fields.any { it.arraySize?.referencesIdentifier(nameInSource)==true }
|
||||
override fun copy() = StructDecl(name, fields.map { it.copy() }.toTypedArray(), isPrivate, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun memsize(sizer: IMemSizer): Int = fields.sumOf { field ->
|
||||
if(field.isArray)
|
||||
sizer.memorySize(field.type, field.arraySize!!)
|
||||
sizer.memorySize(field.type, field.constSize() ?: 1)
|
||||
else
|
||||
sizer.memorySize(field.type, 1)
|
||||
}
|
||||
override fun sameas(other: ISubType): Boolean = other is StructDecl && other.name==name && other.fields.contentEquals(fields)
|
||||
override fun sameas(other: ISubType): Boolean {
|
||||
if(other !is StructDecl || other.name!=name || other.fields.size!=fields.size)
|
||||
return false
|
||||
return fields.zip(other.fields).all { (a, b) ->
|
||||
a.name==b.name && a.type==b.type && when {
|
||||
a.arraySize==null && b.arraySize==null -> true
|
||||
a.arraySize!=null && b.arraySize!=null -> a.arraySize!! isSameAs b.arraySize!!
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFieldType(name: String): DataType? = fields.firstOrNull { it.name==name }?.type
|
||||
fun getField(name: String): StructField? = fields.firstOrNull { it.name==name }
|
||||
@@ -540,7 +557,7 @@ class StructDecl(override val name: String, val fields: Array<StructField>, val
|
||||
fields.fold(0) { offset, field ->
|
||||
if (field.name == fieldname)
|
||||
return offset.toUByte()
|
||||
val numElements = if(field.isArray) field.arraySize!! else 1
|
||||
val numElements = if(field.isArray) field.constSize() ?: 1 else 1
|
||||
offset + sizer.memorySize(field.type, numElements)
|
||||
}
|
||||
return null
|
||||
@@ -548,8 +565,10 @@ class StructDecl(override val name: String, val fields: Array<StructField>, val
|
||||
}
|
||||
|
||||
|
||||
data class StructField(val type: DataType, val name: String, val arraySize: Int? = null) {
|
||||
data class StructField(val type: DataType, val name: String, var arraySize: ArrayIndex? = null) {
|
||||
val isArray: Boolean get() = arraySize != null
|
||||
fun constSize(): Int? = arraySize?.constIndex()
|
||||
fun copy(): StructField = StructField(type, name, arraySize?.copy())
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -380,6 +380,7 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(struct: StructDecl, parent: Node) {
|
||||
track(before(struct, parent), struct, parent)
|
||||
struct.fields.forEach { it.arraySize?.accept(this) }
|
||||
track(after(struct, parent), struct, parent)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ interface IAstVisitor {
|
||||
}
|
||||
|
||||
fun visit(struct: StructDecl) {
|
||||
struct.fields.forEach { it.arraySize?.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(field: StructFieldRef) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package prog8.parser
|
||||
|
||||
/**
|
||||
* Provides the complete set of Prog8 reserved words, derived from the
|
||||
* ANTLR grammar vocabulary so it never goes out of sync with the language.
|
||||
*/
|
||||
object Prog8Keywords {
|
||||
val reservedWords: Set<String> by lazy {
|
||||
val words = sortedSetOf<String>()
|
||||
val vocab = Prog8ANTLRParser.VOCABULARY
|
||||
for (i in 0..vocab.maxTokenType) {
|
||||
val literal = vocab.getLiteralName(i) ?: continue
|
||||
// literal is like "'for'" or "'^^'"; keep only word-like ones longer than 1 char
|
||||
if (literal.length >= 2 && literal.startsWith("'") && literal.endsWith("'")) {
|
||||
val word = literal.substring(1, literal.length - 1)
|
||||
if (word.length > 1 && word.matches(Regex("[a-z][a-z_0-9]*"))) {
|
||||
words.add(word)
|
||||
}
|
||||
}
|
||||
}
|
||||
words
|
||||
}
|
||||
}
|
||||
@@ -250,16 +250,13 @@ Major breaking changes that require code modifications when upgrading:
|
||||
- **Fuzzy library search** — ``-libsearch`` now automatically attempts a fuzzy search if no exact matches are found.
|
||||
|
||||
|
||||
**v12.3** — Not released yet
|
||||
- Laying groundwork for new code generation targets. Still highly experimental.
|
||||
**v12.3** — August 2026
|
||||
- **Module search order rework** — replaced alphabetical search with a deterministic, prioritized list; filesystem now searched before internal libraries.
|
||||
- **New experimental 6502 code generator** based on the IR, enabled with ``-newcodegen`` (do not use for production code yet).
|
||||
- **m68k code generator** — a full experimental m68k code generator will follow in a later version.
|
||||
- **New experimental 6502 code generator** based on the IR, enabled with ``-newcodegen`` (experimental, generates pretty bad/buggy code still!)
|
||||
- **Embedded arrays in structs** — structs can now contain arrays as fields.
|
||||
- **``@bank`` accepts a subroutine name** — for dynamic / overlay banking with a bank manager
|
||||
- **New library routines** — ``floats.mod()``; ``sys.MAX_LONG`` / ``sys.MIN_LONG``; PET32 ``petsnd`` playback functions, ``REST`` constant, ``set_gap()``, enharmonic flat note symbols; PET32 ``sys.set_irq()`` / ``sys.restore_irq()``; serial module: configurable baud rate.
|
||||
- **Breaking changes** — ``-nostdlib`` removed; ``bankmanager`` renamed to ``bankselector``; ``swap`` is now a statement.
|
||||
- **Search Path Comparison**:
|
||||
|
||||
|
||||
*This document summarizes major and minor releases. Bugfix releases (e.g., v12.0.1, v12.1.1) are omitted for brevity.*
|
||||
@@ -125,7 +125,7 @@ scoped names always need to be fully scoped (because they always start in the gl
|
||||
.. index:: pair: Identifiers; Aliases
|
||||
|
||||
The ``alias`` statement makes it easier to refer to symbols from other places, and they can save
|
||||
you from having to type the fully scoped name everytime you need to access that symbol.
|
||||
you from having to type the fully scoped name every time you need to access that symbol.
|
||||
Aliases can be created in any scope except at the module level.
|
||||
An alias is created with ``alias <name> = <target>`` and then you can use ``<name>`` as if it were ``<target>``.
|
||||
It is possible to alias variables, labels and subroutines, and even whole blocks and words
|
||||
@@ -196,7 +196,7 @@ to access a variable from a nested subroutine::
|
||||
}
|
||||
|
||||
**:index:`Aliases`** make it easier to refer to symbols from other places. They save
|
||||
you from having to type the fully scoped name everytime you need to access that symbol.
|
||||
you from having to type the fully scoped name every time you need to access that symbol.
|
||||
Aliases can be created in any scope except at the module level.
|
||||
You can create and use an alias with the ``alias`` statement like so::
|
||||
|
||||
|
||||
@@ -140,8 +140,12 @@ dealing with all of them separately. You first define the struct type like so::
|
||||
bool elite
|
||||
}
|
||||
|
||||
You can use boolean fields, numeric fields (byte, word, long, float), and pointer fields (including str, which is translated into ^^ubyte).
|
||||
You cannot nest struct types, but inline arrays are supported as fields. 2D arrays are not allowed as struct fields.
|
||||
Allowed field datatypes: all numeric types (``byte``, ``ubyte``, ``word``, ``uword``, ``long``, ``float``),
|
||||
``bool``, ``str``, typed pointers to those or to other structs, and inline 1D arrays of these element types.
|
||||
Not allowed: struct instances themselves (structs cannot be nested, use a pointer to the struct instead),
|
||||
arrays of typed pointers such as ``^^Enemy[4]`` (use untyped pointers instead), and 2D arrays.
|
||||
The typed pointer array and 2D array restrictions are limitations of the current compiler; they may or may not be lifted in a future version.
|
||||
|
||||
Fields in a struct are 'packed' (meaning the values are placed back-to-back in memory), and placed in memory in order of declaration. This guarantees exact size and place of the fields.
|
||||
``sizeof()`` knows how to calculate the combined size of a struct, and ``offsetof()`` can be used to get the byte offset of a given field in the struct.
|
||||
The size of a struct cannot exceed 1 memory page (256 bytes).
|
||||
|
||||
+10
-4
@@ -9,27 +9,33 @@ Future Things and Ideas
|
||||
- symboldump: some sort of javadocs generated from the p8 source files (instead of just the function signatures). Use markdown for formatting, not html.
|
||||
- when implementing unsigned longs: remove the (multiple?) "TODO "hack" to allow unsigned long constants to be used as values for signed longs, without needing a cast
|
||||
- struct/ptr: implement the remaining TODOs in PointerAssignmentsGen.
|
||||
- struct/ptr: support pointer to pointer?
|
||||
- struct/ptr: support arrays of typed pointers as struct fields (e.g. ``^^Enemy[4] foos``); currently rejected by AstChecker with "arrays of typed pointers are currently not supported", untyped pointers must be used instead.
|
||||
- struct/ptr: support 2D arrays as struct fields; currently a parser error ("2D arrays are not allowed as struct fields"), even though 2D arrays are supported for regular variables (see variables.rst).
|
||||
- struct/ptr: typed function pointers (simplified): ``&&subroutine`` returns opaque typed pointer (no ``funcptr`` keyword), assignment allowed where target type is inferred, calling via ``ptr(args)``. No explicit signature syntax. Useful mostly for 68000 targets.
|
||||
- struct/ptr: really fixing the pointer dereferencing issues (cursed hybrid between IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator"
|
||||
- struct/ptr: (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer. (type checking now fails on stuff like func().field and func().next.field)
|
||||
- struct/ptr: (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer as assignment target (LHS). RHS value case ``a = func().field`` / ``a = func().next.field`` now works for both VM and 6502 via generic ``peek`` desugar (``AstExpressions.kt:280``, ``CodeDesugarer.kt:654``); still fails for ``func().field = a`` / ``func().next.field = a`` which requires ``Prog8ANTLR.g4:239`` ``assign_target`` grammar change to allow ``functioncall '.' field`` chain (see ``compiler/test/TestPointers.kt`` xtest).
|
||||
- Make all constants long by default? or not? (remove type name altogether), reduce to target type implicitly if the actual value fits. -> long-consts branch
|
||||
This will break some existing programs that depend on value wraparound, but gives more intuitive constant number handling.
|
||||
Can give descriptive error message for old syntax that still includes the type name?
|
||||
- add documentation for more library modules instead of just linking to the source code
|
||||
- sizeof(pointer) is now always 2 (an uword), make this a variable in the ICompilationTarget so that it could be 4 at the time we might ad a 32-bits 68000 target for example. Much code assumes word size addresses though.
|
||||
- sizeof(pointer) is now always 2 (an uword), make this a variable in the ICompilationTarget so that it could be 4 at the time we might ad a 32-bits 68000 target for example. Much code assumes word size addresses though. (already done on new-codegens branch)
|
||||
- add float support to the configurable compiler targets. Restrictions: just have "cbm-style floats" as an option (to that it can slot into the current float codegen), where all you have to specify is the addresses of AYINT and GIVAYF and FADDT and all their friends.
|
||||
- Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first), maybe only when qualified symbol starts with '.' such as: .local.value = 33
|
||||
- implement the signed remainder byte and word routines on 6502 (virtual target already has them working)
|
||||
- implement the signed divmod byte and word routines on 6502 (virtual target already has them working)
|
||||
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
|
||||
(this is already done hardcoded for several of the builtin functions)
|
||||
- migrate CLI argument parsing from the obsolete kotlinx-cli library to Clikt (com.github.ajalt:clikt)
|
||||
- migrate CLI argument parsing from the obsolete kotlinx-cli library to Clikt (com.github.ajalt:clikt) (already done on new-codegens branch)
|
||||
- add new directives ``%bssaddress`` and ``%slabsaddress`` to set the memory address for the BSS area and memory slabs (analogous to ``%address`` for program load address).
|
||||
Note: these should be mutually exclusive with the existing CLI options (``-varsgolden``, ``-varshigh``, ``-slabsgolden``, ``-slabshigh``)
|
||||
because the CLI options are target-aware shorthands (set bank symbols, do bounds checking against predefined ranges)
|
||||
while the directives are raw addresses — they'd conflict if both specified for the same area.
|
||||
- the c64 sprite multiplexer example may need timing adjustments after compiler changes (not a compiler bug — cycle-exact C64 code is inherently fragile)
|
||||
- once 'new-codegen' branch is merged, re-enable bunch of xtest() in TestConst.kt
|
||||
- add an ``atexit`` mechanism: like ``defer`` but runs only at program exit, regardless of which exit path is taken (fall-through, ``sys.exit``, poweroff).
|
||||
Mainly useful for library-owned cleanup (heap pools, opened devices/libraries/windows on the amiga500 target) without requiring the main program to call cleanup explicitly;
|
||||
``defer`` cannot do this because it is bound to subroutine scope. Prototype as a stdlib module first (fixed-capacity registry of subroutine addresses + explicit ``run_all()``, LIFO order),
|
||||
consider promoting to a language feature that invokes the handlers automatically in each target's program epilogue.
|
||||
|
||||
|
||||
Romable (%option romable)
|
||||
|
||||
@@ -417,7 +417,8 @@ set it all to the same value, or maybe even simply assign the individual element
|
||||
|
||||
Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.)
|
||||
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
|
||||
for instance.
|
||||
for instance. The same applies to the names of the builtin functions (such as ``memory``, ``mkword``,
|
||||
``peek`` and ``poke``): they cannot be redefined as a variable, block, subroutine, struct or label.
|
||||
|
||||
Using the ``in`` operator you can easily check if a value is present in an array,
|
||||
example: ``if choice in [1,2,3,4] {....}``
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
%encoding iso
|
||||
%import textio
|
||||
|
||||
Data {
|
||||
struct Record {
|
||||
ubyte id
|
||||
ubyte[4] data
|
||||
word[2] scores
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.iso()
|
||||
|
||||
; Initialize struct instance
|
||||
^^Data.Record rec = ^^Data.Record : [1, 10, 20, 30, 40, 1000, 2000]
|
||||
|
||||
txt.print("Record ID: ")
|
||||
txt.print_ub(rec.id)
|
||||
txt.print("\n")
|
||||
|
||||
txt.print("Data[0]: ")
|
||||
txt.print_ub(rec.data[0])
|
||||
txt.print("\n")
|
||||
|
||||
txt.print("Score[1]: ")
|
||||
txt.print_w(rec.scores[1])
|
||||
txt.print("\n")
|
||||
|
||||
; Modify array fields
|
||||
rec.data[2] = 99
|
||||
txt.print("Modified Data[2]: ")
|
||||
txt.print_ub(rec.data[2])
|
||||
txt.print("\n")
|
||||
|
||||
txt.print("Sizeof Record: ")
|
||||
txt.print_ub(sizeof(Data.Record))
|
||||
txt.print("\n")
|
||||
|
||||
txt.print("Offsetof id: ")
|
||||
txt.print_ub(offsetof(Data.Record.id))
|
||||
txt.print("\n")
|
||||
|
||||
txt.print("Offsetof data: ")
|
||||
txt.print_ub(offsetof(Data.Record.data))
|
||||
txt.print("\n")
|
||||
|
||||
txt.print("Offsetof scores: ")
|
||||
txt.print_ub(offsetof(Data.Record.scores))
|
||||
txt.print("\n")
|
||||
|
||||
sys.poweroff_system()
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,11 @@ main {
|
||||
word[] ycoor = [ -100, -100, 100, 100, -100, -100, 100, 100 ]
|
||||
word[] zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
word[len(xcoor)] rotatedx
|
||||
word[len(ycoor)] rotatedy
|
||||
; storage for rotated Z (needed for sprite sort and attributes)
|
||||
word[len(zcoor)] rotatedz
|
||||
; precomputed screen coordinates (perspective-corrected) for drawing
|
||||
ubyte[len(xcoor)] screenx
|
||||
ubyte[len(ycoor)] screeny
|
||||
|
||||
|
||||
sub start() {
|
||||
@@ -68,9 +69,14 @@ main {
|
||||
ubyte @zp i
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
; don't normalize by dividing by 128, instead keep some precision for perspective calc later
|
||||
rotatedx[i] = (Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i])
|
||||
rotatedy[i] = (Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i])
|
||||
rotatedz[i] = (Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i])
|
||||
word rx = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
word ry = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
|
||||
|
||||
; perspective projection, done once per vertex
|
||||
word persp = 300 + rotatedz[i]/256
|
||||
screenx[i] = rx / persp + width/2 as ubyte + 20
|
||||
screeny[i] = ry / persp + height/2 as ubyte + 40
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,15 +93,15 @@ main {
|
||||
for i1 in 0 to i {
|
||||
ubyte i2 = i1+1
|
||||
if(rotatedz[i1] > rotatedz[i2]) {
|
||||
word @zp temp = rotatedx[i1]
|
||||
rotatedx[i1] = rotatedx[i2]
|
||||
rotatedx[i2] = temp
|
||||
temp = rotatedy[i1]
|
||||
rotatedy[i1] = rotatedy[i2]
|
||||
rotatedy[i2] = temp
|
||||
temp = rotatedz[i1]
|
||||
word @zp tempz = rotatedz[i1]
|
||||
rotatedz[i1] = rotatedz[i2]
|
||||
rotatedz[i2] = temp
|
||||
rotatedz[i2] = tempz
|
||||
ubyte @zp tempx = screenx[i1]
|
||||
screenx[i1] = screenx[i2]
|
||||
screenx[i2] = tempx
|
||||
ubyte @zp tempy = screeny[i1]
|
||||
screeny[i1] = screeny[i2]
|
||||
screeny[i2] = tempy
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,11 +110,8 @@ main {
|
||||
|
||||
for i in 0 to 7 {
|
||||
word @zp zc = rotatedz[i]
|
||||
word persp = 300+zc/256
|
||||
ubyte sx = rotatedx[i] / persp + width/2 as ubyte + 20
|
||||
ubyte sy = rotatedy[i] / persp + height/2 as ubyte + 40
|
||||
|
||||
c64.SPXYW[i] = mkword(sy, sx)
|
||||
c64.SPXYW[i] = mkword(screeny[i], screenx[i])
|
||||
|
||||
; because we want speed, we don't use the dynamic c64.set_sprite_ptr() here
|
||||
if(zc < 30*128)
|
||||
|
||||
+13
-16
@@ -10,10 +10,11 @@ main {
|
||||
word[] ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
|
||||
word[] zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
word[len(xcoor)] rotatedx
|
||||
word[len(ycoor)] rotatedy
|
||||
; storage for rotated Z (needed for back/front classification)
|
||||
word[len(zcoor)] rotatedz
|
||||
; precomputed screen coordinates (perspective-corrected) for drawing
|
||||
byte[len(xcoor)] screenx
|
||||
byte[len(ycoor)] screeny
|
||||
|
||||
sub start() {
|
||||
|
||||
@@ -64,9 +65,14 @@ main {
|
||||
ubyte @zp i
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
; don't normalize by dividing by 128, instead keep some precision for perspective calc later
|
||||
rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
word rx = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
word ry = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
|
||||
|
||||
; perspective projection, done once per vertex
|
||||
word persp = 900 + (rotatedz[i]>>5)
|
||||
screenx[i] = rx / persp as byte + txt.DEFAULT_WIDTH/2
|
||||
screeny[i] = ry / persp as byte + txt.DEFAULT_HEIGHT/2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,27 +83,18 @@ main {
|
||||
|
||||
ubyte @zp i
|
||||
word @zp rz
|
||||
word @zp persp
|
||||
byte sx
|
||||
byte sy
|
||||
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
rz = rotatedz[i]
|
||||
if rz >= 10 {
|
||||
persp = 900 + rz/32
|
||||
sx = rotatedx[i] / persp as byte + txt.DEFAULT_WIDTH/2
|
||||
sy = rotatedy[i] / persp as byte + txt.DEFAULT_HEIGHT/2
|
||||
txt.setcc(sx as ubyte, sy as ubyte, 46, 7)
|
||||
txt.setcc(screenx[i] as ubyte, screeny[i] as ubyte, 46, 7)
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
rz = rotatedz[i]
|
||||
if rz < 10 {
|
||||
persp = 900 + rz/32
|
||||
sx = rotatedx[i] / persp as byte + txt.DEFAULT_WIDTH/2
|
||||
sy = rotatedy[i] / persp as byte + txt.DEFAULT_HEIGHT/2
|
||||
txt.setcc(sx as ubyte, sy as ubyte, 81, 7)
|
||||
txt.setcc(screenx[i] as ubyte, screeny[i] as ubyte, 81, 7)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-16
@@ -11,10 +11,11 @@ main {
|
||||
float[] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
|
||||
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
float[len(xcoor)] rotatedx
|
||||
float[len(ycoor)] rotatedy
|
||||
; storage for rotated Z (needed for back/front classification)
|
||||
float[len(zcoor)] rotatedz
|
||||
; precomputed screen coordinates (perspective-corrected) for drawing
|
||||
ubyte[len(xcoor)] screenx
|
||||
ubyte[len(ycoor)] screeny
|
||||
|
||||
sub start() {
|
||||
float time=0.0
|
||||
@@ -64,9 +65,14 @@ main {
|
||||
|
||||
ubyte @zp i
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
float rx = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
float ry = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
|
||||
|
||||
; perspective projection, done once per vertex
|
||||
float persp = (5.0+rotatedz[i])/(txt.DEFAULT_HEIGHT as float)
|
||||
screenx[i] = rx / persp + txt.DEFAULT_WIDTH/2.0 as ubyte
|
||||
screeny[i] = ry / persp + txt.DEFAULT_HEIGHT/2.0 as ubyte
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,27 +82,18 @@ main {
|
||||
; first the points on the back, then the points on the front (painter algorithm)
|
||||
ubyte @zp i
|
||||
float rz
|
||||
float persp
|
||||
ubyte sx
|
||||
ubyte sy
|
||||
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
rz = rotatedz[i]
|
||||
if rz >= 0.1 {
|
||||
persp = (5.0+rz)/(txt.DEFAULT_HEIGHT as float)
|
||||
sx = rotatedx[i] / persp + txt.DEFAULT_WIDTH/2.0 as ubyte
|
||||
sy = rotatedy[i] / persp + txt.DEFAULT_HEIGHT/2.0 as ubyte
|
||||
txt.setcc(sx, sy, 46, 1)
|
||||
txt.setcc(screenx[i], screeny[i], 46, 1)
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
rz = rotatedz[i]
|
||||
if rz < 0.1 {
|
||||
persp = (5.0+rz)/(txt.DEFAULT_HEIGHT as float)
|
||||
sx = rotatedx[i] / persp + txt.DEFAULT_WIDTH/2.0 as ubyte
|
||||
sy = rotatedy[i] / persp + txt.DEFAULT_HEIGHT/2.0 as ubyte
|
||||
txt.setcc(sx, sy, 81, 1)
|
||||
txt.setcc(screenx[i], screeny[i], 81, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-13
@@ -11,10 +11,9 @@ main {
|
||||
word[] ycoor = [ -100, -100, 100, 100, -100, -100, 100, 100 ]
|
||||
word[] zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
word[len(xcoor)] rotatedx
|
||||
word[len(ycoor)] rotatedy
|
||||
word[len(zcoor)] rotatedz
|
||||
; storage for screen coordinates (perspective-corrected)
|
||||
word[len(xcoor)] screenx
|
||||
word[len(ycoor)] screeny
|
||||
|
||||
; edges
|
||||
ubyte[] edgesFrom = [ 0, 2, 6, 4, 1, 3, 7, 5, 0, 2, 6, 4]
|
||||
@@ -69,9 +68,14 @@ main {
|
||||
ubyte @zp i
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
; don't normalize by dividing by 128, instead keep some precision for perspective calc later
|
||||
rotatedx[i] = (Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i])
|
||||
rotatedy[i] = (Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i])
|
||||
rotatedz[i] = (Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i])
|
||||
word rx = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
word ry = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
word rz = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
|
||||
|
||||
; perspective projection, done once per vertex
|
||||
word persp = 256 + (rz>>8)
|
||||
screenx[i] = rx / persp + graphics.WIDTH/2 as uword
|
||||
screeny[i] = ry / persp + graphics.HEIGHT/2 as ubyte
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,12 +84,7 @@ main {
|
||||
for i in len(edgesFrom) -1 downto 0 {
|
||||
ubyte @zp vFrom = edgesFrom[i]
|
||||
ubyte @zp vTo = edgesTo[i]
|
||||
word @zp persp1 = 256 + rotatedz[vFrom]/256
|
||||
word @zp persp2 = 256 + rotatedz[vTo]/256
|
||||
graphics.line(rotatedx[vFrom] / persp1 + graphics.WIDTH/2 as uword,
|
||||
rotatedy[vFrom] / persp1 + graphics.HEIGHT/2 as ubyte,
|
||||
rotatedx[vTo] / persp2 + graphics.WIDTH/2 as uword,
|
||||
rotatedy[vTo] / persp2 + graphics.HEIGHT/2 as ubyte)
|
||||
graphics.line(screenx[vFrom] as uword, screeny[vFrom] as ubyte, screenx[vTo] as uword, screeny[vTo] as ubyte)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,76 +83,38 @@ main {
|
||||
for i in shipdata.totalNumberOfEdges -1 downto 0 {
|
||||
ubyte @zp vFrom = shipdata.edgesFrom[i]
|
||||
ubyte @zp vTo = shipdata.edgesTo[i]
|
||||
word persp1 = 200 + matrix_math.rotatedz[vFrom]/256
|
||||
word persp2 = 200 + matrix_math.rotatedz[vTo]/256
|
||||
monogfx.line(matrix_math.rotatedx[vFrom] / persp1 + screen_width/2 as uword,
|
||||
matrix_math.rotatedy[vFrom] / persp1 + screen_height/2 as uword,
|
||||
matrix_math.rotatedx[vTo] / persp2 + screen_width/2 as uword,
|
||||
matrix_math.rotatedy[vTo] / persp2 + screen_height/2 as uword,
|
||||
monogfx.line(matrix_math.screenx[vFrom] as uword,
|
||||
matrix_math.screeny[vFrom] as uword,
|
||||
matrix_math.screenx[vTo] as uword,
|
||||
matrix_math.screeny[vTo] as uword,
|
||||
true)
|
||||
}
|
||||
}
|
||||
|
||||
sub draw_lines_hiddenremoval() {
|
||||
; complex drawing routine that draws the ship model based on its faces,
|
||||
; where it uses the surface normals to determine visibility.
|
||||
sys.memset(edgestodraw, shipdata.totalNumberOfEdges, 1)
|
||||
ubyte @zp edgeIdx = 0
|
||||
ubyte @zp pointIdx = 0
|
||||
; determine visibility of each face
|
||||
ubyte faceNumber
|
||||
for faceNumber in shipdata.totalNumberOfFaces -1 downto 0 {
|
||||
if matrix_math.facing_away_fast_but_imprecise(pointIdx) { ;; can also use matrix_math.facing_away_slow_but_precise(pointIdx)
|
||||
; don't draw this face, fast-forward over the edges and points
|
||||
edgeIdx += 3 ; every face hast at least 3 edges
|
||||
while shipdata.facesEdges[edgeIdx]!=255 {
|
||||
edgeIdx++
|
||||
}
|
||||
edgeIdx++
|
||||
pointIdx += 3 ; every face has at least 3 points
|
||||
while shipdata.facesPoints[pointIdx]!=255 {
|
||||
pointIdx++
|
||||
}
|
||||
pointIdx++
|
||||
} else {
|
||||
; draw this face
|
||||
ubyte @zp e1 = shipdata.facesEdges[edgeIdx]
|
||||
edgeIdx ++
|
||||
ubyte @zp e2 = shipdata.facesEdges[edgeIdx]
|
||||
edgeIdx ++
|
||||
ubyte @zp e3 = shipdata.facesEdges[edgeIdx]
|
||||
edgeIdx ++
|
||||
if edgestodraw[e1]
|
||||
draw_edge(e1)
|
||||
if edgestodraw[e2]
|
||||
draw_edge(e2)
|
||||
while e3!=255 {
|
||||
if edgestodraw[e3]
|
||||
draw_edge(e3)
|
||||
e3 = shipdata.facesEdges[edgeIdx]
|
||||
edgeIdx ++
|
||||
}
|
||||
; skip the rest of the facesPoints, we don't need them here anymore
|
||||
pointIdx += 3 ; every face has at least 3 points
|
||||
while shipdata.facesPoints[pointIdx]!=255 {
|
||||
pointIdx++
|
||||
}
|
||||
pointIdx++
|
||||
for faceNumber in 0 to matrix_math.FACE_COUNT-1 {
|
||||
matrix_math.face_visible[faceNumber] = not matrix_math.facing_away_fast_but_imprecise(matrix_math.facePointIdx[faceNumber])
|
||||
}
|
||||
|
||||
; draw every edge that belongs to at least one visible face
|
||||
ubyte @zp edgeIdx
|
||||
for edgeIdx in 0 to shipdata.totalNumberOfEdges-1 {
|
||||
if matrix_math.face_visible[matrix_math.edgeFaceA[edgeIdx]] or
|
||||
(matrix_math.edgeFaceB[edgeIdx] != 255 and matrix_math.face_visible[matrix_math.edgeFaceB[edgeIdx]]) {
|
||||
draw_edge(edgeIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool[shipdata.totalNumberOfEdges] edgestodraw
|
||||
|
||||
sub draw_edge(ubyte edgeidx) {
|
||||
edgestodraw[edgeidx] = false
|
||||
ubyte vFrom = shipdata.edgesFrom[edgeidx]
|
||||
ubyte vTo = shipdata.edgesTo[edgeidx]
|
||||
word persp1 = 170 + matrix_math.rotatedz[vFrom]/256
|
||||
word persp2 = 170 + matrix_math.rotatedz[vTo]/256
|
||||
monogfx.line(matrix_math.rotatedx[vFrom] / persp1 + screen_width/2 as uword,
|
||||
matrix_math.rotatedy[vFrom] / persp1 + screen_height/2 as uword,
|
||||
matrix_math.rotatedx[vTo] / persp2 + screen_width/2 as uword,
|
||||
matrix_math.rotatedy[vTo] / persp2 + screen_height/2 as uword,
|
||||
monogfx.line(matrix_math.screenx[vFrom] as uword,
|
||||
matrix_math.screeny[vFrom] as uword,
|
||||
matrix_math.screenx[vTo] as uword,
|
||||
matrix_math.screeny[vTo] as uword,
|
||||
true)
|
||||
}
|
||||
}
|
||||
@@ -164,6 +126,17 @@ matrix_math {
|
||||
word[shipdata.totalNumberOfPoints] rotatedx
|
||||
word[shipdata.totalNumberOfPoints] rotatedy
|
||||
word[shipdata.totalNumberOfPoints] rotatedz
|
||||
; precomputed screen coordinates (perspective-corrected) for drawing
|
||||
word[shipdata.totalNumberOfPoints] screenx
|
||||
word[shipdata.totalNumberOfPoints] screeny
|
||||
|
||||
const ubyte FACE_COUNT = 22
|
||||
; start index of each face in shipdata.facesPoints
|
||||
ubyte[FACE_COUNT] facePointIdx = [ 0, 4, 9, 13, 17, 22, 26, 30, 35, 39, 43, 47, 51, 59, 64, 69, 73, 77, 82, 87, 92, 97 ]
|
||||
; for each edge, the one or two incident faces (255 means no second face)
|
||||
ubyte[shipdata.totalNumberOfEdges] edgeFaceA = [ 0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10, 11, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 21, 21 ]
|
||||
ubyte[shipdata.totalNumberOfEdges] edgeFaceB = [ 1, 5, 2, 12, 7, 4, 9, 3, 11, 4, 11, 12, 8, 6, 7, 10, 12, 10, 9, 12, 12, 12, 12, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 20, 255, 21, 19, 21, 255, 20, 255, 255, 255, 255 ]
|
||||
bool[FACE_COUNT] face_visible
|
||||
|
||||
sub rotate_vertices(ubyte ax, ubyte ay, ubyte az) {
|
||||
; rotate around origin (0,0,0)
|
||||
@@ -176,18 +149,18 @@ matrix_math {
|
||||
word wcosc = math.cos8(az)
|
||||
word wsinc = math.sin8(az)
|
||||
|
||||
word wcosa_sinb = wcosa*wsinb / 128
|
||||
word wsina_sinb = wsina*wsinb / 128
|
||||
word wcosa_sinb = wcosa*wsinb >> 7
|
||||
word wsina_sinb = wsina*wsinb >> 7
|
||||
|
||||
word Axx = wcosa*wcosb / 128
|
||||
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) / 128
|
||||
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) / 128
|
||||
word Ayx = wsina*wcosb / 128
|
||||
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) / 128
|
||||
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) / 128
|
||||
word Axx = wcosa*wcosb >> 7
|
||||
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) >> 7
|
||||
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) >> 7
|
||||
word Ayx = wsina*wcosb >> 7
|
||||
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) >> 7
|
||||
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) >> 7
|
||||
word Azx = -wsinb
|
||||
word Azy = wcosb*wsinc / 128
|
||||
word Azz = wcosb*wcosc / 128
|
||||
word Azy = wcosb*wsinc >> 7
|
||||
word Azz = wcosb*wcosc >> 7
|
||||
|
||||
ubyte @zp i
|
||||
for i in 0 to shipdata.totalNumberOfPoints-1 {
|
||||
@@ -195,6 +168,13 @@ matrix_math {
|
||||
rotatedx[i] = Axx*shipdata.xcoor[i] + Axy*shipdata.ycoor[i] + Axz*shipdata.zcoor[i]
|
||||
rotatedy[i] = Ayx*shipdata.xcoor[i] + Ayy*shipdata.ycoor[i] + Ayz*shipdata.zcoor[i]
|
||||
rotatedz[i] = Azx*shipdata.xcoor[i] + Azy*shipdata.ycoor[i] + Azz*shipdata.zcoor[i]
|
||||
|
||||
; perspective projection, done once per vertex instead of once per edge
|
||||
word persp = 170 + rotatedz[i]/256
|
||||
if persp < 32
|
||||
persp = 32
|
||||
screenx[i] = rotatedx[i] / persp + 160 as uword
|
||||
screeny[i] = rotatedy[i] / persp + 120 as uword
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,12 +187,12 @@ matrix_math {
|
||||
edgePointsIdx++
|
||||
ubyte p3 = shipdata.facesPoints[edgePointsIdx]
|
||||
|
||||
word p1x = rotatedx[p1] / 128
|
||||
word p1y = rotatedy[p1] / 128
|
||||
word p2x = rotatedx[p2] / 128
|
||||
word p2y = rotatedy[p2] / 128
|
||||
word p3x = rotatedx[p3] / 128
|
||||
word p3y = rotatedy[p3] / 128
|
||||
word p1x = rotatedx[p1] >> 7
|
||||
word p1y = rotatedy[p1] >> 7
|
||||
word p2x = rotatedx[p2] >> 7
|
||||
word p2y = rotatedy[p2] >> 7
|
||||
word p3x = rotatedx[p3] >> 7
|
||||
word p3y = rotatedy[p3] >> 7
|
||||
return (p2x-p3x)*(p1y-p3y) - (p2y-p3y)*(p1x-p3x) > 0
|
||||
}
|
||||
|
||||
@@ -225,12 +205,12 @@ matrix_math {
|
||||
ubyte p3 = shipdata.facesPoints[edgePointsIdx]
|
||||
|
||||
; Calculate two edge vectors of the triangle (scaled by 2)
|
||||
word v1x = (rotatedx[p2] - rotatedx[p1])/128
|
||||
word v1y = (rotatedy[p2] - rotatedy[p1])/128
|
||||
word v1z = (rotatedz[p2] - rotatedz[p1])/128
|
||||
word v2x = (rotatedx[p3] - rotatedx[p1])/128
|
||||
word v2y = (rotatedy[p3] - rotatedy[p1])/128
|
||||
word v2z = (rotatedz[p3] - rotatedz[p1])/128
|
||||
word v1x = (rotatedx[p2] - rotatedx[p1]) >> 7
|
||||
word v1y = (rotatedy[p2] - rotatedy[p1]) >> 7
|
||||
word v1z = (rotatedz[p2] - rotatedz[p1]) >> 7
|
||||
word v2x = (rotatedx[p3] - rotatedx[p1]) >> 7
|
||||
word v2y = (rotatedy[p3] - rotatedy[p1]) >> 7
|
||||
word v2z = (rotatedz[p3] - rotatedz[p1]) >> 7
|
||||
|
||||
; Calculate surface normal using cross product: N = V1 x V2 (scaled by 4)
|
||||
; Note: because of lack of precision in the 16 bit word math, we need to use floating point math here.... :-(
|
||||
|
||||
+13
-16
@@ -28,27 +28,18 @@ main {
|
||||
|
||||
ubyte @zp i
|
||||
word @zp rz
|
||||
word @zp persp
|
||||
byte sx
|
||||
byte sy
|
||||
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz >= 10 {
|
||||
persp = 400 + rz/64
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + txt.DEFAULT_WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + txt.DEFAULT_HEIGHT/2
|
||||
txt.setcc(sx as ubyte, sy as ubyte, 46, 7)
|
||||
txt.setcc(matrix_math.screenx[i] as ubyte, matrix_math.screeny[i] as ubyte, 46, 7)
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz < 10 {
|
||||
persp = 400 + rz/64
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + txt.DEFAULT_WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + txt.DEFAULT_HEIGHT/2
|
||||
txt.setcc(sx as ubyte, sy as ubyte, 81, 7)
|
||||
txt.setcc(matrix_math.screenx[i] as ubyte, matrix_math.screeny[i] as ubyte, 81, 7)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,10 +53,11 @@ matrix_math {
|
||||
word[] ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
|
||||
word[] zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
word[len(xcoor)] rotatedx
|
||||
word[len(ycoor)] rotatedy
|
||||
; storage for rotated Z (needed for back/front classification)
|
||||
word[len(zcoor)] rotatedz
|
||||
; precomputed screen coordinates (perspective-corrected) for drawing
|
||||
byte[len(xcoor)] screenx
|
||||
byte[len(ycoor)] screeny
|
||||
|
||||
sub rotate_vertices(ubyte ax, ubyte ay, ubyte az) {
|
||||
; rotate around origin (0,0,0)
|
||||
@@ -94,9 +86,14 @@ matrix_math {
|
||||
ubyte @zp i
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
; don't normalize by dividing by 128, instead keep some precision for perspective calc later
|
||||
rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
word rx = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
word ry = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
|
||||
|
||||
; perspective projection, done once per vertex
|
||||
word persp = 400 + (rotatedz[i]>>6)
|
||||
screenx[i] = rx / persp as byte + txt.DEFAULT_WIDTH/2
|
||||
screeny[i] = ry / persp as byte + txt.DEFAULT_HEIGHT/2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
|
||||
main {
|
||||
ubyte[8] scratch
|
||||
ubyte counter
|
||||
|
||||
sub start() {
|
||||
scratch[0] = 10
|
||||
scratch[1] = 20
|
||||
|
||||
; Pattern 1: PtSub 2-byte-param spill
|
||||
; both args are array-indexed reads (non-simple) -> needAsaveForExpr true for both
|
||||
two_byte_sub(scratch[0], scratch[1])
|
||||
|
||||
; Pattern 2: Asmsub XY-A spill
|
||||
; XY gets simple identifier, A gets value from a non-inlinable function call
|
||||
asm_xy_a(scratch, get_value(counter))
|
||||
|
||||
; Pattern 3: Asmsub Y-X spill
|
||||
; Y gets number, X gets binary expression with non-inlinable function call
|
||||
asm_y_x(0, get_value(counter) + 5)
|
||||
|
||||
; Pattern 4: Asmsub Y-X-A spill
|
||||
; Y gets number, X gets binary expression with non-inlinable function call
|
||||
; (A gets simple number, no further spill)
|
||||
asm_y_x_a(0, get_value(counter) + 3, 42)
|
||||
|
||||
; Pattern 5: Library asmsub Y-X (txt.plot) spill
|
||||
; Y gets simple number, X gets complex expression -> Y saved before eval of X
|
||||
txt.plot(0, get_value(counter) + 5)
|
||||
|
||||
; Pattern 6: Library asmsub X-Y-A (txt.setchr) spill
|
||||
; Y (row) gets simple number, X (col) gets complex expression -> Y saved before eval of X
|
||||
; (A gets simple number, no further spill)
|
||||
txt.setchr(get_value(counter), 0, 42)
|
||||
}
|
||||
|
||||
sub two_byte_sub(ubyte a, ubyte b) {
|
||||
counter += a
|
||||
counter += b
|
||||
}
|
||||
|
||||
sub get_value(ubyte x) -> ubyte {
|
||||
if x==0
|
||||
return 42
|
||||
return x
|
||||
}
|
||||
|
||||
asmsub asm_xy_a(uword ptr @XY, ubyte val @A) clobbers(A) {
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub asm_y_x(ubyte col @Y, ubyte row @X) {
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub asm_y_x_a(ubyte col @Y, ubyte row @X, ubyte ch @A) clobbers(A) {
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
txt {
|
||||
asmsub plot(ubyte col @ Y, ubyte row @ X) {
|
||||
%asm {{
|
||||
clc
|
||||
jmp $FFF0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setchr(ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
+4
-18
@@ -3,24 +3,10 @@
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
; Issue 4: @(...) is always an UNSIGNED byte read. The optimizer rewrite
|
||||
; @(&x) -> x (ConstantFoldingOptimizer / MemoryOptimizers) must not turn the
|
||||
; unsigned memory read into the signed variable, which would sign-extend on widen.
|
||||
; Reading -1 from memory (0xFF) must give 255, not 65535.
|
||||
byte @shared sv = -1 ; memory holds 0xFF
|
||||
word a = @(&sv) ; zero-extends the unsigned byte to 255
|
||||
txt.print("mem-read signed byte -1: ")
|
||||
txt.print_w(a)
|
||||
txt.print(" (raw 0xFF; zero-extends to 255)\n")
|
||||
|
||||
; Contrast: an UNSIGNED byte read via @(&x) is correctly zero-extended.
|
||||
ubyte @shared uv = 255 ; memory also holds 0xFF
|
||||
word b = @(&uv) ; zero-extends to 255
|
||||
txt.print("mem-read unsigned byte 255: ")
|
||||
txt.print_w(b)
|
||||
txt.print(" (raw 0xFF; zero-extends to 255)\n")
|
||||
|
||||
txt.print("done\n")
|
||||
;sys.poweroff_system()
|
||||
for cx16.r0L in 5 downto 0 {
|
||||
txt.print_ub(cx16.r0L)
|
||||
txt.spc()
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,4 +4,4 @@ org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
org.gradle.configuration-cache=false
|
||||
kotlin.code.style=official
|
||||
version=12.3
|
||||
version=12.3.4
|
||||
@@ -38,9 +38,9 @@ private object IRStSymbolicReferenceXml {
|
||||
is IRStSymbolicReference.BoolValue -> throw InternalCompilerException("bool in word array")
|
||||
}
|
||||
|
||||
fun formatForStructField(ref: IRStSymbolicReference): String = when(ref) {
|
||||
fun formatForStructField(ref: IRStSymbolicReference, dt: DataType): String = when(ref) {
|
||||
is IRStSymbolicReference.BoolValue -> if(ref.value) "1" else "0"
|
||||
is IRStSymbolicReference.Numeric -> ref.value.toInt().toHex()
|
||||
is IRStSymbolicReference.Numeric -> if(dt.isFloat) ref.value.toString() else ref.value.toInt().toHex()
|
||||
is IRStSymbolicReference.Symbol -> "@${ref.name}"
|
||||
}
|
||||
}
|
||||
@@ -458,7 +458,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
listOf(field.type)
|
||||
}
|
||||
val values = expandedTypes.zip(instance.values).map {(dt, value) ->
|
||||
val valuestr = IRStSymbolicReferenceXml.formatForStructField(value.value)
|
||||
val valuestr = IRStSymbolicReferenceXml.formatForStructField(value.value, dt)
|
||||
dt to valuestr
|
||||
}
|
||||
append(values.joinToString(",") { "${it.first}:${it.second}" })
|
||||
|
||||
@@ -1116,6 +1116,8 @@ data class IRInstruction(
|
||||
return IRDataType.BYTE
|
||||
if(opcode==Opcode.SQRT)
|
||||
return IRDataType.WORD
|
||||
if(opcode==Opcode.STOREZX)
|
||||
return IRDataType.BYTE
|
||||
}
|
||||
if(opcode in setOf(Opcode.JUMPI, Opcode.CALLI, Opcode.STOREZI, Opcode.LSIGW, Opcode.MSIGW))
|
||||
return IRDataType.WORD
|
||||
|
||||
@@ -17,7 +17,7 @@ fun DataType.irTypeString(length: UInt?): String {
|
||||
BaseDataType.WORD -> "word"
|
||||
BaseDataType.LONG -> "long"
|
||||
BaseDataType.FLOAT -> "float"
|
||||
BaseDataType.STR -> "ubyte[$lengthStr]" // here string doesn't exist as a seperate datatype anymore
|
||||
BaseDataType.STR -> "ubyte[$lengthStr]" // here string doesn't exist as a separate datatype anymore
|
||||
BaseDataType.POINTER -> "uword"
|
||||
BaseDataType.ARRAY_POINTER -> "uword"
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.ints.shouldBeGreaterThan
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.OutputType
|
||||
import prog8.code.core.ZeropageType
|
||||
import prog8.code.target.Cx16Target
|
||||
@@ -188,6 +190,53 @@ load.b r1,#0
|
||||
struct.structName shouldBe "re.State"
|
||||
}
|
||||
|
||||
test("test IR writer preserves decimal precision in float struct fields") {
|
||||
// regression test: struct field floats used to be serialized via .toInt().toHex()
|
||||
// which truncated the decimal portion (e.g. 1463.87 became 1463.0 in the loaded VM memory)
|
||||
val target = Cx16Target()
|
||||
val tempdir = Path(System.getProperty("java.io.tmpdir"))
|
||||
val options = CompilationOptions.builder(target)
|
||||
.output(OutputType.RAW)
|
||||
.zeropage(ZeropageType.DONTUSE)
|
||||
.noSysInit(true)
|
||||
.compilerVersion("99.99")
|
||||
.loadAddress(target.PROGRAM_LOAD_ADDRESS)
|
||||
.memtopAddress(0xffffu)
|
||||
.outputDir(tempdir)
|
||||
.build()
|
||||
val program = IRProgram("unittest-float-struct", IRSymbolTable(), options, target)
|
||||
val structName = "main.Country"
|
||||
val fields = listOf(
|
||||
IRStStructField(DataType.forDt(BaseDataType.UWORD), "name"),
|
||||
IRStStructField(DataType.forDt(BaseDataType.FLOAT), "population"),
|
||||
IRStStructField(DataType.forDt(BaseDataType.UWORD), "area")
|
||||
)
|
||||
program.st.add(IRStStructDef(structName, fields, 8u))
|
||||
val instanceName = "main.india_instance"
|
||||
val population = 1463.87
|
||||
val area = 3287
|
||||
program.st.add(IRStStructInstance(
|
||||
instanceName,
|
||||
structName,
|
||||
listOf(
|
||||
IRStructInitValue(BaseDataType.UWORD, IRStSymbolicReference.Numeric(0.0)),
|
||||
IRStructInitValue(BaseDataType.FLOAT, IRStSymbolicReference.Numeric(population)),
|
||||
IRStructInitValue(BaseDataType.UWORD, IRStSymbolicReference.Numeric(area.toDouble()))
|
||||
),
|
||||
8u
|
||||
))
|
||||
val writer = IRFileWriter(program, null)
|
||||
val generatedFile = writer.write()
|
||||
val program2 = IRFileReader().read(generatedFile)
|
||||
generatedFile.deleteExisting()
|
||||
val instance = program2.st.allStructInstances().single { it.name == instanceName }
|
||||
instance.values.size shouldBe 3
|
||||
val floatValue = instance.values[1].value as IRStSymbolicReference.Numeric
|
||||
floatValue.value shouldBe population
|
||||
val intValue = instance.values[2].value as IRStSymbolicReference.Numeric
|
||||
intValue.value shouldBe area.toDouble()
|
||||
}
|
||||
|
||||
test("test IR reader parses loadhr/storehr sN immediate encoding") {
|
||||
val source="""<?xml version="1.0" encoding="utf-8"?>
|
||||
<PROGRAM NAME="test-sn-immediate" COMPILERVERSION="99.99">
|
||||
|
||||
@@ -156,26 +156,36 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
}
|
||||
}
|
||||
|
||||
fun isSimple(): Boolean {
|
||||
// An expression is "simple" if evaluating it has no side effects and requires no temporary
|
||||
// scratch storage or register preservation by the caller.
|
||||
// The optional zeroPageVarCheck predicate (when given) additionally allows pointer derefs that can be
|
||||
// evaluated without any codegen temporaries: the start pointer must already be a zeropage variable,
|
||||
// so that the value can be read with a simple indirect indexed load from the pointer variable itself.
|
||||
fun isSimple(zeroPageVarCheck: ((String) -> Boolean)? = null): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> this.arrayIndexExpr==null || this.arrayIndexExpr?.isSimple()==true
|
||||
is PtAddressOf -> this.arrayIndexExpr==null || this.arrayIndexExpr?.isSimple(zeroPageVarCheck)==true
|
||||
is PtArray -> true
|
||||
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
|
||||
is PtBinaryExpression -> false
|
||||
is PtContainmentCheck -> false
|
||||
is PtFunctionCall -> if (this.builtin && name in SimpleBuiltinFunctions) args.all { it.isSimple() } else false
|
||||
is PtFunctionCall -> if (this.builtin && name in SimpleBuiltinFunctions) args.all { it.isSimple(zeroPageVarCheck) } else false
|
||||
is PtIdentifier -> true
|
||||
is PtIrRegister -> true
|
||||
is PtMemoryByte -> address is PtNumber
|
||||
is PtBool -> true
|
||||
is PtNumber -> true
|
||||
is PtPrefix -> value.isSimple()
|
||||
is PtPrefix -> value.isSimple(zeroPageVarCheck)
|
||||
is PtRange -> true
|
||||
is PtString -> true
|
||||
is PtPointerDeref -> false
|
||||
is PtTypeCast -> value.isSimple()
|
||||
is PtIfExpression -> condition.isSimple() && truevalue.isSimple() && falsevalue.isSimple()
|
||||
is PtBranchCondExpression -> truevalue.isSimple() && falsevalue.isSimple()
|
||||
is PtPointerDeref ->
|
||||
if(zeroPageVarCheck==null || type.isFloat || !zeroPageVarCheck(startpointer.name))
|
||||
false
|
||||
else
|
||||
chain.isEmpty() // *pp : lda (pp),y (chain is never empty with derefLast=false)
|
||||
|| (chain.size==1 && !derefLast) // pp.field : lda (pp+offset),y
|
||||
is PtTypeCast -> value.isSimple(zeroPageVarCheck)
|
||||
is PtIfExpression -> condition.isSimple(zeroPageVarCheck) && truevalue.isSimple(zeroPageVarCheck) && falsevalue.isSimple(zeroPageVarCheck)
|
||||
is PtBranchCondExpression -> truevalue.isSimple(zeroPageVarCheck) && falsevalue.isSimple(zeroPageVarCheck)
|
||||
is PtConstant -> true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user