mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c7256a443 | ||
|
|
97420b28e5 | ||
|
|
1467c7039d | ||
|
|
d319badc6c | ||
|
|
65d6c1c438 | ||
|
|
abeefb5655 | ||
|
|
50fecbcebe | ||
|
|
4fe8b72d42 | ||
|
|
f3b060df51 | ||
|
|
09d1cb6925 | ||
|
|
54fa72fa98 | ||
|
|
fd62fe7511 | ||
|
|
bfb34dff62 | ||
|
|
817b623596 | ||
|
|
f6dbeb1f63 | ||
|
|
c4b9bdd33f | ||
|
|
68e0d5f1b5 | ||
|
|
4939e3df55 | ||
|
|
19f19f3880 | ||
|
|
ad0c767ea8 | ||
|
|
ed5f4d5855 | ||
|
|
c2f5d37486 | ||
|
|
231b50dacb | ||
|
|
a71895cbe8 | ||
|
|
a8bede17b2 | ||
|
|
f6c8e693a5 | ||
|
|
9461e4088c | ||
|
|
efd73fd10d | ||
|
|
ddf6e84a1a | ||
|
|
633d6c34e2 | ||
|
|
6e7fbc6683 | ||
|
|
124ea1230b | ||
|
|
8b48a295b6 | ||
|
|
d285d37fdb | ||
|
|
8bb927b483 | ||
|
|
1af4cd0d63 | ||
|
|
db2f28c4cd |
No files matched your search
@@ -0,0 +1,17 @@
|
||||
# An .aiignore file follows the same syntax as a .gitignore file.
|
||||
# .gitignore documentation: https://git-scm.com/docs/gitignore
|
||||
|
||||
# you can ignore files
|
||||
.DS_Store
|
||||
*.log
|
||||
*.tmp
|
||||
*.bin
|
||||
*.prg
|
||||
|
||||
# or folders
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
output/
|
||||
.gradle/
|
||||
.kotlin/
|
||||
@@ -0,0 +1,234 @@
|
||||
; Binary Search Tree.
|
||||
; It's a simple implementation for test/demonstration purposes of the pointer support;
|
||||
; no balancing is done and memory is not freed when elements are removed.
|
||||
|
||||
%import textio
|
||||
|
||||
btree {
|
||||
|
||||
sub benchmark(uword max_time) -> uword {
|
||||
txt.nl()
|
||||
cbm.SETTIM(0,0,0)
|
||||
uword score
|
||||
while cbm.RDTIM16() < max_time {
|
||||
bench_operations()
|
||||
txt.chrout('.')
|
||||
score++
|
||||
}
|
||||
txt.nl()
|
||||
return score
|
||||
}
|
||||
|
||||
sub bench_operations() {
|
||||
arena.freeall()
|
||||
btree.root = 0
|
||||
|
||||
for cx16.r0 in [321, 719, 194, 550, 187, 203, 520, 562, 221, 676, 97, 852, 273, 326, 589, 606, 275, 794, 63, 716]
|
||||
btree.add(cx16.r0)
|
||||
|
||||
cx16.r0L = btree.size()
|
||||
btree.process_tree_inorder()
|
||||
btree.process_tree_preorder()
|
||||
|
||||
void btree.contains(203)
|
||||
void btree.contains(204)
|
||||
void btree.contains(605)
|
||||
void btree.contains(606)
|
||||
|
||||
btree.remove(9999)
|
||||
btree.remove(97)
|
||||
btree.remove(187)
|
||||
btree.remove(203)
|
||||
btree.remove(275)
|
||||
btree.remove(321)
|
||||
btree.remove(520)
|
||||
btree.remove(562)
|
||||
btree.remove(606)
|
||||
btree.remove(719)
|
||||
btree.remove(794)
|
||||
|
||||
cx16.r0L = btree.size()
|
||||
btree.process_tree_inorder()
|
||||
btree.process_tree_preorder()
|
||||
}
|
||||
|
||||
struct Node {
|
||||
^^Node left
|
||||
^^Node right
|
||||
uword value
|
||||
}
|
||||
|
||||
^^Node root = 0
|
||||
|
||||
sub add(uword value) {
|
||||
^^Node node = arena.alloc(sizeof(Node))
|
||||
node.value = value
|
||||
node.left = node.right = 0
|
||||
|
||||
if root==0
|
||||
root=node
|
||||
else {
|
||||
^^Node parent = root
|
||||
repeat {
|
||||
if parent.value >= value {
|
||||
if parent.left!=0
|
||||
parent = parent.left
|
||||
else {
|
||||
parent.left = node
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if parent.right!=0
|
||||
parent = parent.right
|
||||
else {
|
||||
parent.right = node
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub contains(uword value) -> bool {
|
||||
^^Node r = root
|
||||
while r!=0 {
|
||||
if r.value==value
|
||||
return true
|
||||
if r.value>value
|
||||
r = r.left
|
||||
else
|
||||
r = r.right
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub size() -> ubyte {
|
||||
ubyte count
|
||||
|
||||
if root!=0
|
||||
count_node(root)
|
||||
|
||||
return count
|
||||
|
||||
sub count_node(^^Node r) {
|
||||
count++
|
||||
if r.left!=0 {
|
||||
sys.pushw(r)
|
||||
count_node(r.left)
|
||||
r = sys.popw()
|
||||
}
|
||||
if r.right!=0 {
|
||||
sys.pushw(r)
|
||||
count_node(r.right)
|
||||
r = sys.popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub remove(uword value) {
|
||||
; note: we don't deallocate the memory from the node, for simplicity sake
|
||||
^^Node n = root
|
||||
^^Node parent = 0
|
||||
while n!=0 {
|
||||
if n.value==value {
|
||||
if n.left==0
|
||||
replacechild(parent, n, n.right)
|
||||
else if n.right==0
|
||||
replacechild(parent, n, n.left)
|
||||
else {
|
||||
; Both left & right subtrees are present.
|
||||
; N = node to delete.
|
||||
; Find N's successor S. (N's right subtree's minimum element)
|
||||
; Attach N's left subtree to S.left (S doesn't have a left child)
|
||||
; Attach N's right subtree to Parent in place of N.
|
||||
^^Node successor = find_successor(n)
|
||||
successor.left = n.left
|
||||
replacechild(parent, n, n.right)
|
||||
}
|
||||
return
|
||||
}
|
||||
parent = n
|
||||
if n.value>value
|
||||
n = n.left
|
||||
else
|
||||
n = n.right
|
||||
}
|
||||
|
||||
sub find_successor(^^Node p) -> ^^Node {
|
||||
^^Node succ = p
|
||||
p = p.right
|
||||
while p!=0 {
|
||||
succ = p
|
||||
p = p.left
|
||||
}
|
||||
return succ
|
||||
}
|
||||
|
||||
sub replacechild(^^Node p, ^^Node child, ^^Node newchild) {
|
||||
if p.left==child
|
||||
p.left = newchild
|
||||
else
|
||||
p.right = newchild
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub process_tree_inorder() {
|
||||
if root!=0
|
||||
process_tree(root)
|
||||
|
||||
sub process_tree(^^Node r) {
|
||||
if r.left!=0 {
|
||||
sys.pushw(r)
|
||||
process_tree(r.left)
|
||||
r = sys.popw()
|
||||
}
|
||||
cx16.r0 = r.value
|
||||
if r.right!=0 {
|
||||
sys.pushw(r)
|
||||
process_tree(r.right)
|
||||
r = sys.popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub process_tree_preorder() {
|
||||
if root!=0
|
||||
process_tree(root,0)
|
||||
|
||||
sub process_tree(^^Node r, ubyte depth) {
|
||||
cx16.r0 = r.value
|
||||
if r.left!=0 {
|
||||
sys.pushw(r)
|
||||
sys.push(depth)
|
||||
process_tree(r.left, depth+1)
|
||||
depth = sys.pop()
|
||||
r = sys.popw()
|
||||
}
|
||||
if r.right!=0 {
|
||||
sys.pushw(r)
|
||||
sys.push(depth)
|
||||
process_tree(r.right, depth+1)
|
||||
depth = sys.pop()
|
||||
r = sys.popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
arena {
|
||||
; extremely trivial arena allocator (that never frees)
|
||||
uword buffer = memory("arena", 2000, 0)
|
||||
uword next = buffer
|
||||
|
||||
sub alloc(ubyte size) -> uword {
|
||||
defer next += size
|
||||
return next
|
||||
}
|
||||
|
||||
sub freeall() {
|
||||
next = buffer
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
%import b_textelite
|
||||
%import b_maze
|
||||
%import b_sprites
|
||||
%import b_btree
|
||||
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
@@ -67,10 +68,6 @@ main {
|
||||
benchmark_score[benchmark_number] = circles.draw(false, 300)
|
||||
benchmark_number++
|
||||
|
||||
; announce_benchmark("circles with kernal")
|
||||
; benchmark_score[benchmark_number] = circles.draw(true, 300)
|
||||
; benchmark_number++
|
||||
|
||||
announce_benchmark("text-elite")
|
||||
benchmark_score[benchmark_number] = textelite.bench(120)
|
||||
benchmark_number++
|
||||
@@ -79,13 +76,17 @@ main {
|
||||
benchmark_score[benchmark_number] = animsprites.benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("btree-struct-pointers")
|
||||
benchmark_score[benchmark_number] = btree.benchmark(200)
|
||||
benchmark_number++
|
||||
|
||||
benchmark_names[benchmark_number] = 0
|
||||
benchmark_score[benchmark_number] = 0
|
||||
|
||||
cx16.set_screen_mode(3)
|
||||
txt.uppercase()
|
||||
txt.color2(1, 6)
|
||||
uword final_score
|
||||
uword total_score
|
||||
benchmark_number = 0
|
||||
txt.print("\nscore benchmark\n\n")
|
||||
do {
|
||||
@@ -93,14 +94,14 @@ main {
|
||||
txt.print_uw(benchmark_score[benchmark_number])
|
||||
txt.column(6)
|
||||
txt.print(benchmark_names[benchmark_number])
|
||||
final_score += benchmark_score[benchmark_number]
|
||||
total_score += benchmark_score[benchmark_number]
|
||||
txt.nl()
|
||||
benchmark_number++
|
||||
} until benchmark_names[benchmark_number]==0
|
||||
|
||||
txt.print("\n\nfinal score : ")
|
||||
txt.print_uw(final_score)
|
||||
txt.nl()
|
||||
txt.print("\n\ntotal score : ")
|
||||
txt.print_uw(total_score)
|
||||
txt.print(" (higher=better)\n")
|
||||
|
||||
sub announce_benchmark(str name) {
|
||||
benchmark_names[benchmark_number] = name
|
||||
|
||||
@@ -5,9 +5,10 @@ import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
|
||||
|
||||
// the automatically generated module where all string literals are interned to:
|
||||
const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings"
|
||||
|
||||
val PROG8_CONTAINER_MODULES = arrayOf(INTERNED_STRINGS_MODULENAME) // option to add more if needed one day
|
||||
|
||||
// all automatically generated labels everywhere need to have the same label name prefix:
|
||||
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
|
||||
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
|
||||
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
|
||||
"offsetof" to FSignature(true, BaseDataType.UBYTE, FParam("field", BaseDataType.UBYTE)),
|
||||
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
|
||||
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),
|
||||
|
||||
@@ -252,6 +252,14 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
newValue.add(newAddr)
|
||||
}
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
// could be a struct instance or memory slab "allocation"
|
||||
if (elt.name != "prog8_lib_structalloc" && elt.name != "memory")
|
||||
throw AssemblyError("weird array value element $elt")
|
||||
else {
|
||||
newValue.add(elt)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird array value element $elt")
|
||||
}
|
||||
}
|
||||
@@ -550,7 +558,7 @@ class AsmGen6502Internal (
|
||||
} else {
|
||||
return if (allocator.isZpVar((target as PtNamedNode).scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
loadAFromZpPointerVar(sourceName, true)
|
||||
loadAFromZpPointerVar(sourceName)
|
||||
sourceName
|
||||
} else {
|
||||
out("""
|
||||
@@ -609,7 +617,7 @@ class AsmGen6502Internal (
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadAFromZpPointerVar(zpPointerVar: String, keepY: Boolean) {
|
||||
internal fun loadAFromZpPointerVar(zpPointerVar: String, keepY: Boolean=false) {
|
||||
if (isTargetCpu(CpuType.CPU65C02))
|
||||
out(" lda ($zpPointerVar)")
|
||||
else {
|
||||
@@ -805,6 +813,9 @@ class AsmGen6502Internal (
|
||||
}
|
||||
|
||||
internal fun assignExpressionTo(value: PtExpression, target: AsmAssignTarget) {
|
||||
|
||||
// this is basically the fallback assignment routine, it is RARELY called
|
||||
|
||||
when {
|
||||
target.datatype.isByteOrBool -> {
|
||||
if (value.asConstInteger()==0) {
|
||||
|
||||
@@ -463,15 +463,19 @@ private fun optimizeStoreLoadSame(
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
|
||||
// all 3 registers: lda VALUE + sta SOMEWHERE + lda VALUE -> last load can be eliminated
|
||||
// all 3 registers: lda VALUE + sta SOMEWHERE + lda VALUE -> last load can be eliminated IF NOT IO ADDRESS
|
||||
if (first.startsWith("lda ") && second.startsWith("sta ") && third.startsWith("lda ") ||
|
||||
first.startsWith("ldx ") && second.startsWith("stx ") && third.startsWith("ldx ") ||
|
||||
first.startsWith("ldy ") && second.startsWith("sty ") && third.startsWith("ldy ")
|
||||
) {
|
||||
val firstVal = first.substring(4).trimStart()
|
||||
val thirdVal = third.substring(4).trimStart()
|
||||
if (firstVal == thirdVal)
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
if (firstVal == thirdVal) {
|
||||
val address = getAddressArg(third, symbolTable)
|
||||
if (address != null && !machine.isIOAddress(address)) {
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mods
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.StMemorySlabBlockName
|
||||
import prog8.code.StStructInstanceBlockName
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
@@ -385,7 +387,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val name = (fcall.args[0] as PtString).value
|
||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
|
||||
|
||||
val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position)
|
||||
val slabname = PtIdentifier("$StMemorySlabBlockName.memory_$name", DataType.UWORD, fcall.position)
|
||||
val addressOf = PtAddressOf(DataType.pointer(BaseDataType.UBYTE), false, fcall.position)
|
||||
addressOf.add(slabname)
|
||||
addressOf.parent = fcall
|
||||
@@ -399,9 +401,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
if(discardResult)
|
||||
throw AssemblyError("should not discard result of struct allocation at $fcall")
|
||||
// ... don't need to pay attention to args here because struct instance is put together elsewhere we just have to get a pointer to it
|
||||
val slabname = SymbolTable.labelnameForStructInstance(fcall)
|
||||
val prefix = if(fcall.args.isEmpty()) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
|
||||
val labelname = PtIdentifier("$prefix.${SymbolTable.labelnameForStructInstance(fcall)}", fcall.type, fcall.position)
|
||||
val addressOf = PtAddressOf(fcall.type, true, fcall.position)
|
||||
addressOf.add(PtIdentifier(slabname, fcall.type, fcall.position))
|
||||
addressOf.add(labelname)
|
||||
addressOf.parent = fcall
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, fcall.type, expression = addressOf)
|
||||
val target = AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen)
|
||||
|
||||
@@ -210,7 +210,7 @@ internal class ProgramAndVarsGen(
|
||||
private fun memorySlabs() {
|
||||
if(symboltable.allMemorySlabs.isNotEmpty()) {
|
||||
asmgen.out("; memory slabs\n .section BSS_SLABS")
|
||||
asmgen.out("prog8_slabs\t.block")
|
||||
asmgen.out("$StMemorySlabBlockName\t.block")
|
||||
for (slab in symboltable.allMemorySlabs) {
|
||||
if (slab.align > 1u)
|
||||
asmgen.out("\t.align ${slab.align.toHex()}")
|
||||
@@ -434,6 +434,7 @@ internal class ProgramAndVarsGen(
|
||||
val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() }
|
||||
asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n")
|
||||
asmgen.out(" .section BSS\n")
|
||||
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
|
||||
instancesNoInit.forEach {
|
||||
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
|
||||
val zerovalues = structtype.fields.map { field ->
|
||||
@@ -445,11 +446,17 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send BSS\n")
|
||||
|
||||
asmgen.out("; struct instances with initialization values\n")
|
||||
asmgen.out(" .section STRUCTINSTANCES\n")
|
||||
instances.forEach { asmgen.out("${it.name} .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n") }
|
||||
asmgen.out("$StStructInstanceBlockName .block\n")
|
||||
instances.forEach {
|
||||
val instancename = it.name.substringAfter('.')
|
||||
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send STRUCTINSTANCES\n")
|
||||
}
|
||||
|
||||
@@ -866,7 +873,7 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
}
|
||||
dt.isSplitWordArray -> {
|
||||
if(dt.elementType().isUnsignedWord) {
|
||||
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
|
||||
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
@@ -914,7 +921,7 @@ internal class ProgramAndVarsGen(
|
||||
private fun zeroFilledArray(numElts: Int): StArray {
|
||||
val values = mutableListOf<StArrayElement>()
|
||||
repeat(numElts) {
|
||||
values.add(StArrayElement(0.0, null, null))
|
||||
values.add(StArrayElement(0.0, null, null,null,null))
|
||||
}
|
||||
return values
|
||||
}
|
||||
@@ -972,7 +979,7 @@ internal class ProgramAndVarsGen(
|
||||
val number = it.number!!.toInt()
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
}
|
||||
dt.isArray && dt.elementType().isUnsignedWord -> array.map {
|
||||
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
|
||||
if(it.number!=null) {
|
||||
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
@@ -984,8 +991,15 @@ internal class ProgramAndVarsGen(
|
||||
else
|
||||
asmgen.asmSymbolName(addrOfSymbol)
|
||||
}
|
||||
else
|
||||
else if(it.structInstance!=null) {
|
||||
asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}")
|
||||
}
|
||||
else if(it.structInstanceUninitialized!=null) {
|
||||
asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}")
|
||||
}
|
||||
else {
|
||||
throw AssemblyError("weird array elt")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ internal enum class TargetStorageKind {
|
||||
ARRAY,
|
||||
MEMORY,
|
||||
REGISTER,
|
||||
POINTER, // wherever the pointer variable points to
|
||||
VOID // assign nothing - used in multi-value assigns for void placeholders
|
||||
POINTER,
|
||||
VOID // assign nothing - used in multi-value assigns for void placeholders
|
||||
}
|
||||
|
||||
internal enum class SourceStorageKind {
|
||||
|
||||
@@ -5,6 +5,7 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
import prog8.codegen.cpu6502.VariableAllocator
|
||||
import kotlin.math.log2
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(
|
||||
@@ -392,7 +393,7 @@ internal class AssignmentAsmGen(
|
||||
|
||||
// fallback assignmen through temporary pointer var
|
||||
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
}
|
||||
|
||||
@@ -1145,6 +1146,14 @@ internal class AssignmentAsmGen(
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, expr.type.isSigned)
|
||||
if (value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" jsr prog8_math.mul_byte_${value}")
|
||||
else if(value in powersOfTwoInt) {
|
||||
val shifts = log2(value.toDouble()).toInt()
|
||||
if(shifts>=8) {
|
||||
asmgen.out(" lda #0")
|
||||
} else {
|
||||
repeat(shifts) { asmgen.out(" asl a") }
|
||||
}
|
||||
}
|
||||
else
|
||||
asmgen.out(" ldy #$value | jsr prog8_math.multiply_bytes")
|
||||
assignRegisterByte(target, CpuRegister.A, false, true)
|
||||
@@ -1155,7 +1164,26 @@ internal class AssignmentAsmGen(
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
|
||||
asmgen.out(" jsr prog8_math.mul_word_${value}")
|
||||
}
|
||||
else if(value in powersOfTwoInt) {
|
||||
val shifts = log2(value.toDouble()).toInt()
|
||||
if(shifts>=16) {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
|
||||
asmgen.out(" lda #0 | ldy #0")
|
||||
} else {
|
||||
if(target.kind==TargetStorageKind.VARIABLE && target.datatype.isWord) {
|
||||
assignExpressionToVariable(expr.left, target.asmVarname, target.datatype)
|
||||
repeat(shifts) { asmgen.out(" asl ${target.asmVarname} | rol ${target.asmVarname}+1") }
|
||||
return true
|
||||
} else {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
|
||||
asmgen.out(" sty P8ZP_SCRATCH_REG")
|
||||
repeat(shifts) { asmgen.out(" asl a | rol P8ZP_SCRATCH_REG") }
|
||||
asmgen.out(" ldy P8ZP_SCRATCH_REG")
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type.isSigned)
|
||||
if(expr.definingBlock()!!.options.veraFxMuls){
|
||||
// cx16 verafx hardware mul
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "cx16.r1")
|
||||
@@ -2217,7 +2245,7 @@ $endLabel""")
|
||||
|
||||
fun assignViaExprEval(addressExpression: PtExpression) {
|
||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
asmgen.out(" ldy #0")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
@@ -257,16 +257,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
internal fun inplaceModification(target: PtrTarget, operator: String, value: AsmAssignSource) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
if(target.dt.isByte) inplaceByteAdd(target, value)
|
||||
else if(target.dt.isWord) inplaceWordAdd(target, value)
|
||||
else if(target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
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.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if(target.dt.isByte) inplaceByteSub(target, value)
|
||||
else if(target.dt.isWord) inplaceWordSub(target, value)
|
||||
else if(target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
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.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
}
|
||||
}
|
||||
"*" -> {
|
||||
if(target.dt.isByte) TODO("inplaceByteMul(target, value) ${target.position}")
|
||||
@@ -282,22 +292,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
"%" -> TODO("inplace ptr %")
|
||||
"<<" -> {
|
||||
if(target.dt.isByte) TODO("inplaceByteShiftLeft(target, value) ${target.position}")
|
||||
if(target.dt.isByte) inplaceByteShiftLeft(target, value)
|
||||
else if(target.dt.isWord) inplaceWordShiftLeft(target, value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
}
|
||||
">>" -> {
|
||||
if(target.dt.isByte) TODO("inplaceByteShiftRight(target, value) ${target.position}")
|
||||
if(target.dt.isByte) inplaceByteShiftRight(target, value)
|
||||
else if(target.dt.isWord) inplaceWordShiftRight(target, value)
|
||||
else throw AssemblyError("weird dt ${target.position}")
|
||||
}
|
||||
"&", "and" -> {
|
||||
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
|
||||
TODO("inplace ptr &")
|
||||
if(target.dt.isByteOrBool) inplaceByteAnd(target, value)
|
||||
else if(target.dt.isWord) inplaceWordAnd(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
"|", "or" -> {
|
||||
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
|
||||
TODO("inplace ptr |")
|
||||
if(target.dt.isByteOrBool) inplaceByteOr(target, value)
|
||||
else if(target.dt.isWord) inplaceWordOr(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
// byte targets are handled as direct memory access, not a pointer operation anymore however boolean targets are still to be handled here
|
||||
@@ -305,12 +319,6 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
else if(target.dt.isWord) inplaceWordXor(target, value)
|
||||
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
|
||||
}
|
||||
"==" -> TODO("inplace ptr ==")
|
||||
"!=" -> TODO("inplace ptr !=")
|
||||
"<" -> TODO("inplace ptr <")
|
||||
"<=" -> TODO("inplace ptr <=")
|
||||
">" -> TODO("inplace ptr >")
|
||||
">=" -> TODO("inplace ptr >=")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@@ -491,10 +499,9 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
private fun addUnsignedByteOrWordToAY(value: PtExpression, scale: Int?) {
|
||||
// TODO generate more optimal code from this, a bit too much stack and temporary variable juggling seems to be done in some easy cases...
|
||||
|
||||
fun complicatedFallback() {
|
||||
// slow fallback routine that can deal with any expression for value
|
||||
// slow fallback routine that can deal with any expression for value (const number and variable have been dealt with already)
|
||||
|
||||
fun restoreAYandAddAsmVariable(varname: String, isByte: Boolean) {
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false) // msb
|
||||
@@ -693,7 +700,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
|
||||
if(target.dt.isSigned)
|
||||
TODO("signed word shift rigth ${target.position} $value")
|
||||
TODO("signed word shift right ${target.position} $value")
|
||||
|
||||
fun shift1unsigned() {
|
||||
asmgen.out("""
|
||||
@@ -720,21 +727,92 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
require(value.datatype.isByte)
|
||||
val varname = value.asmVarname
|
||||
TODO("<< variable")
|
||||
asmgen.out(" ldx $varname")
|
||||
asmgen.out("-")
|
||||
shift1unsigned()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isWord)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
|
||||
TODO("<< expression")
|
||||
require(value.datatype.isByte)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
|
||||
asmgen.out("-")
|
||||
shift1unsigned()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
require(value.datatype.isWord)
|
||||
require(value.datatype.isByte)
|
||||
val register = value.register!!
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_PTR"))
|
||||
require(register.isWord())
|
||||
TODO("<< register")
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
|
||||
asmgen.out("-")
|
||||
shift1unsigned()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteShiftRight(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
|
||||
if(target.dt.isSigned)
|
||||
TODO("signed byte shift right ${target.position} $value")
|
||||
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(number==1) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
lsr a
|
||||
sta ($zpPtrVar),y""")
|
||||
} else if(number>1) {
|
||||
asmgen.out("""
|
||||
ldx #$number
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- lsr a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isByte)
|
||||
val varname = value.asmVarname
|
||||
asmgen.out("""
|
||||
ldx $varname
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- lsr a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isByte)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- lsr a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
require(value.datatype.isByte)
|
||||
val register = value.register!!
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UWORD, null, target.position, register = RegisterOrPair.X))
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- lsr a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
@@ -768,21 +846,90 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
require(value.datatype.isByte)
|
||||
val varname = value.asmVarname
|
||||
TODO("<< variable")
|
||||
asmgen.out(" ldx $varname")
|
||||
asmgen.out("-")
|
||||
shift1()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isWord)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
|
||||
TODO("<< expression")
|
||||
require(value.datatype.isByte)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
|
||||
asmgen.out("-")
|
||||
shift1()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
require(value.datatype.isWord)
|
||||
require(value.datatype.isByte)
|
||||
val register = value.register!!
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, null, target.position, variableAsmName = "P8ZP_SCRATCH_PTR"))
|
||||
require(register.isWord())
|
||||
TODO("<< register")
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
|
||||
asmgen.out("-")
|
||||
shift1()
|
||||
asmgen.out(" dex | bne -")
|
||||
}
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteShiftLeft(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(number==1) {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
asl a
|
||||
sta ($zpPtrVar),y""")
|
||||
} else if(number>1) {
|
||||
asmgen.out("""
|
||||
ldx #$number
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- asl a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isByte)
|
||||
val varname = value.asmVarname
|
||||
asmgen.out("""
|
||||
ldx $varname
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- asl a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isByte)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.X)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- asl a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
require(value.datatype.isByte)
|
||||
val register = value.register!!
|
||||
asmgen.assignRegister(register, AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE, null, target.position, register = RegisterOrPair.X))
|
||||
asmgen.out("-")
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
- asl a
|
||||
dex
|
||||
bne -
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
@@ -1046,6 +1193,46 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteInc(target: PtrTarget, amount: Int) {
|
||||
require(amount==1 || amount==2)
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out(" lda ($zpPtrVar)")
|
||||
repeat(amount) {
|
||||
asmgen.out(" inc a")
|
||||
}
|
||||
asmgen.out(" sta ($zpPtrVar)")
|
||||
}
|
||||
else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
clc
|
||||
adc #$amount
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteDec(target: PtrTarget, amount: Int) {
|
||||
require(amount==1 || amount==2)
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
asmgen.out(" lda ($zpPtrVar)")
|
||||
repeat(amount) {
|
||||
asmgen.out(" dec a")
|
||||
}
|
||||
asmgen.out(" sta ($zpPtrVar)")
|
||||
}
|
||||
else {
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
sec
|
||||
sbc #$amount
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteAdd(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
when(value.kind) {
|
||||
@@ -1230,48 +1417,222 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
|
||||
fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
|
||||
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
|
||||
val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
|
||||
assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
|
||||
when {
|
||||
target.datatype.isByteOrBool -> {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_PTR),y""")
|
||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
}
|
||||
target.datatype.isWord || target.datatype.isPointer -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
private fun inplaceByteOr(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out("""
|
||||
ldy #1
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
tax
|
||||
lda (P8ZP_SCRATCH_PTR)""")
|
||||
lda ($zpPtrVar)
|
||||
ora #$number
|
||||
sta ($zpPtrVar)""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #1
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
tax
|
||||
dey
|
||||
lda (P8ZP_SCRATCH_PTR),y""")
|
||||
asmgen.assignRegister(RegisterOrPair.AX, target)
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
ora #$number
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
target.datatype.isLong -> {
|
||||
TODO("assign long from pointer to $target ${target.position}")
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
val varname = value.asmVarname
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar)
|
||||
ora $varname
|
||||
sta ($zpPtrVar)""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
ora $varname
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
target.datatype.isFloat -> {
|
||||
// TODO optimize the float copying to avoid having to go through FAC1
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
lda P8ZP_SCRATCH_PTR
|
||||
ldy P8ZP_SCRATCH_PTR+1
|
||||
jsr floats.MOVFM""")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, target)
|
||||
ldy #$offset
|
||||
ora ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt ${target.datatype}")
|
||||
SourceStorageKind.REGISTER -> TODO("register | byte")
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceWordOr(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
ora #<$number
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
ora #>$number
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
val varname = value.asmVarname
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
ora $varname
|
||||
sta ($zpPtrVar),y
|
||||
lda ($zpPtrVar),y
|
||||
ora $varname+1
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isWord)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
ora ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
txa
|
||||
ora ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> TODO("register | word")
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceByteAnd(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar)
|
||||
and #$number
|
||||
sta ($zpPtrVar)""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
and #$number
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
val varname = value.asmVarname
|
||||
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar)
|
||||
and $varname
|
||||
sta ($zpPtrVar)""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
and $varname
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
and ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> TODO("register & byte")
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceWordAnd(target: PtrTarget, value: AsmAssignSource) {
|
||||
val (zpPtrVar, offset) = deref(target.pointer)
|
||||
when(value.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
val number = value.number!!.number.toInt()
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
and #<$number
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
and #>$number
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
val varname = value.asmVarname
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
and $varname
|
||||
sta ($zpPtrVar),y
|
||||
lda ($zpPtrVar),y
|
||||
and $varname+1
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
require(value.datatype.isWord)
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AX)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
and ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
txa
|
||||
and ($zpPtrVar),y
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
SourceStorageKind.REGISTER -> TODO("register & word")
|
||||
else -> throw AssemblyError("weird source value $value")
|
||||
}
|
||||
}
|
||||
|
||||
fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
|
||||
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
|
||||
// val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
|
||||
// assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
|
||||
// when {
|
||||
// target.datatype.isByteOrBool -> {
|
||||
// asmgen.out("""
|
||||
// ldy #0
|
||||
// lda (P8ZP_SCRATCH_PTR),y""")
|
||||
// asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
// }
|
||||
// target.datatype.isWord || target.datatype.isPointer -> {
|
||||
// if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
// asmgen.out("""
|
||||
// ldy #1
|
||||
// lda (P8ZP_SCRATCH_PTR),y
|
||||
// tax
|
||||
// lda (P8ZP_SCRATCH_PTR)""")
|
||||
// else
|
||||
// asmgen.out("""
|
||||
// ldy #1
|
||||
// lda (P8ZP_SCRATCH_PTR),y
|
||||
// tax
|
||||
// dey
|
||||
// lda (P8ZP_SCRATCH_PTR),y""")
|
||||
// asmgen.assignRegister(RegisterOrPair.AX, target)
|
||||
// }
|
||||
// target.datatype.isLong -> {
|
||||
// TODO("assign long from pointer to $target ${target.position}")
|
||||
// }
|
||||
// target.datatype.isFloat -> {
|
||||
// // TODO optimize the float copying to avoid having to go through FAC1
|
||||
// asmgen.out("""
|
||||
// lda P8ZP_SCRATCH_PTR
|
||||
// ldy P8ZP_SCRATCH_PTR+1
|
||||
// jsr floats.MOVFM""")
|
||||
// asmgen.assignRegister(RegisterOrPair.FAC1, target)
|
||||
// }
|
||||
// else -> throw AssemblyError("weird dt ${target.datatype}")
|
||||
// }
|
||||
}
|
||||
|
||||
private fun saveOnStack(regs: RegisterOrPair) {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> {
|
||||
|
||||
@@ -137,63 +137,75 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
val inplaceInstrs = mutableListOf<IRCodeChunkBase>()
|
||||
val (addressReg, fieldOffset) = codeGen.evaluatePointerAddressIntoReg(inplaceInstrs, pointerDeref)
|
||||
val oldvalueReg = codeGen.registers.next(targetDt)
|
||||
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
|
||||
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
|
||||
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
|
||||
operandTr = expressionEval.translateExpression(value)
|
||||
inplaceInstrs += operandTr.chunks
|
||||
}
|
||||
if(targetDt== IRDataType.FLOAT) {
|
||||
if(fieldOffset>0u)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()), null)
|
||||
else
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg), null)
|
||||
when(augAssign.operator) {
|
||||
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"+" -> { /* inplace + is a no-op */ }
|
||||
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
|
||||
|
||||
if((augAssign.operator=="+=" || augAssign.operator=="-=") && value.asConstInteger()==1 || value.asConstInteger()==2) {
|
||||
// INC/DEC optimization instead of ADD/SUB
|
||||
|
||||
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
|
||||
val instr = if(augAssign.operator=="+=") Opcode.INC else Opcode.DEC
|
||||
repeat(value.asConstInteger()!!) {
|
||||
addInstr(inplaceInstrs, IRInstruction(instr, targetDt, reg1 = oldvalueReg), null)
|
||||
}
|
||||
|
||||
} else {
|
||||
if(fieldOffset>0u)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()), null)
|
||||
else
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg), null)
|
||||
when(augAssign.operator) {
|
||||
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"or=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = expressionEval.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
|
||||
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
|
||||
|
||||
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
|
||||
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
|
||||
// for everything except the shortcircuit boolean operators, we can evaluate the value here unconditionally
|
||||
operandTr = expressionEval.translateExpression(value)
|
||||
// note: the instructions to load the value will be placed after the LOADFIELD instruction so that later optimizations about what modification is actually done, are easier
|
||||
}
|
||||
if(targetDt== IRDataType.FLOAT) {
|
||||
|
||||
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
|
||||
inplaceInstrs += operandTr.chunks
|
||||
when(augAssign.operator) {
|
||||
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
|
||||
"+" -> { /* inplace + is a no-op */ }
|
||||
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
|
||||
}
|
||||
"and=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = expressionEval.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
|
||||
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
|
||||
|
||||
} else {
|
||||
|
||||
loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg)
|
||||
inplaceInstrs += operandTr.chunks
|
||||
when(augAssign.operator) {
|
||||
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"<<=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
">>=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.LSRN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
|
||||
"or=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = expressionEval.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
|
||||
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
|
||||
}
|
||||
"and=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = expressionEval.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null)
|
||||
inplaceInstrs += IRCodeChunk(shortcutLabel, null)
|
||||
}
|
||||
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
|
||||
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
|
||||
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
|
||||
"+" -> { /* inplace + is a no-op */ }
|
||||
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
|
||||
}
|
||||
"-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null)
|
||||
"~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null)
|
||||
"not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null)
|
||||
"+" -> { /* inplace + is a no-op */ }
|
||||
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +234,42 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
return chunks
|
||||
}
|
||||
|
||||
private fun loadfield(
|
||||
inplaceInstrs: MutableList<IRCodeChunkBase>,
|
||||
addressReg: Int,
|
||||
fieldOffset: UByte,
|
||||
targetDt: IRDataType,
|
||||
oldvalueReg: Int
|
||||
) {
|
||||
if (targetDt == IRDataType.FLOAT) {
|
||||
if (fieldOffset > 0u)
|
||||
addInstr(
|
||||
inplaceInstrs,
|
||||
IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()),
|
||||
null
|
||||
)
|
||||
else
|
||||
addInstr(
|
||||
inplaceInstrs,
|
||||
IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg),
|
||||
null
|
||||
)
|
||||
} else {
|
||||
if (fieldOffset > 0u)
|
||||
addInstr(
|
||||
inplaceInstrs,
|
||||
IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()),
|
||||
null
|
||||
)
|
||||
else
|
||||
addInstr(
|
||||
inplaceInstrs,
|
||||
IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg),
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
|
||||
val value: PtExpression
|
||||
if(origAssign.operator in PrefixOperators) {
|
||||
@@ -415,10 +463,6 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
|
||||
private fun translateRegularAssign(assignment: PtAssignment): IRCodeChunks {
|
||||
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
||||
val targetIdent = assignment.target.identifier
|
||||
val targetMemory = assignment.target.memory
|
||||
val targetArray = assignment.target.array
|
||||
val targetPointerDeref = assignment.target.pointerDeref
|
||||
val valueDt = irType(assignment.value.type)
|
||||
val targetDt = irType(assignment.target.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
@@ -459,98 +503,104 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
}
|
||||
|
||||
if(targetIdent!=null) {
|
||||
val instruction = if(zero) {
|
||||
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = targetIdent.name)
|
||||
} else {
|
||||
if (targetDt == IRDataType.FLOAT) {
|
||||
require(valueFpRegister>=0)
|
||||
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = targetIdent.name)
|
||||
}
|
||||
else {
|
||||
require(valueRegister>=0)
|
||||
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = targetIdent.name)
|
||||
}
|
||||
}
|
||||
result += IRCodeChunk(null, null).also { it += instruction }
|
||||
return result
|
||||
}
|
||||
else if(targetArray!=null) {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(targetArray.type, null)
|
||||
val variable = targetArray.variable
|
||||
if(variable==null)
|
||||
translateRegularAssignPointerIndexed(result, targetArray.pointerderef!!, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
|
||||
else if(variable.type.isPointer)
|
||||
assignToIndexedSimplePointer(result, variable, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
|
||||
else
|
||||
translateRegularAssignArrayIndexed(result, variable.name, eltSize, targetArray, zero, targetDt, valueRegister, valueFpRegister)
|
||||
return result
|
||||
}
|
||||
else if(targetMemory!=null) {
|
||||
require(targetDt == IRDataType.BYTE) { "must be byte type ${targetMemory.position}"}
|
||||
if(zero) {
|
||||
if(targetMemory.address is PtNumber) {
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (targetMemory.address as PtNumber).number.toInt()) }
|
||||
result += chunk
|
||||
} else {
|
||||
val tr = expressionEval.translateExpression(targetMemory.address)
|
||||
val addressReg = tr.resultReg
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val constAddress = targetMemory.address as? PtNumber
|
||||
if(constAddress!=null) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toInt()), null)
|
||||
return result
|
||||
}
|
||||
val ptrWithOffset = targetMemory.address as? PtBinaryExpression
|
||||
if(ptrWithOffset!=null) {
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
|
||||
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
|
||||
if(constOffset in 0..255) {
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=pointerReg, labelSymbol = ptrName)
|
||||
it += IRInstruction(Opcode.STOREFIELD, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset)
|
||||
}
|
||||
return result
|
||||
with(assignment.target) {
|
||||
when {
|
||||
identifier != null -> {
|
||||
val instruction = if(zero) {
|
||||
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = identifier!!.name)
|
||||
} else {
|
||||
if (targetDt == IRDataType.FLOAT) {
|
||||
require(valueFpRegister>=0)
|
||||
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = identifier!!.name)
|
||||
}
|
||||
else {
|
||||
require(valueRegister>=0)
|
||||
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = identifier!!.name)
|
||||
}
|
||||
}
|
||||
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
|
||||
// STOREIX only works with byte index.
|
||||
val tr = if(offsetTypecast?.value?.type?.isByte==true)
|
||||
expressionEval.translateExpression(offsetTypecast.value)
|
||||
else
|
||||
expressionEval.translateExpression(ptrWithOffset.right)
|
||||
result += IRCodeChunk(null, null).also { it += instruction }
|
||||
return result
|
||||
}
|
||||
memory != null -> {
|
||||
require(targetDt == IRDataType.BYTE) { "must be byte type ${memory!!.position}"}
|
||||
if(zero) {
|
||||
if(memory!!.address is PtNumber) {
|
||||
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (memory!!.address as PtNumber).number.toInt()) }
|
||||
result += chunk
|
||||
} else {
|
||||
val tr = expressionEval.translateExpression(memory!!.address)
|
||||
val addressReg = tr.resultReg
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val constAddress = memory!!.address as? PtNumber
|
||||
if(constAddress!=null) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toInt()), null)
|
||||
return result
|
||||
}
|
||||
val ptrWithOffset = memory!!.address as? PtBinaryExpression
|
||||
if(ptrWithOffset!=null) {
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
|
||||
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
|
||||
if(constOffset in 0..255) {
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=pointerReg, labelSymbol = ptrName)
|
||||
it += IRInstruction(Opcode.STOREFIELD, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
|
||||
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
|
||||
// STOREIX only works with byte index.
|
||||
val tr = if(offsetTypecast?.value?.type?.isByte==true)
|
||||
expressionEval.translateExpression(offsetTypecast.value)
|
||||
else
|
||||
expressionEval.translateExpression(ptrWithOffset.right)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
val tr = expressionEval.translateExpression(memory!!.address)
|
||||
val addressReg = tr.resultReg
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val ptrName = (ptrWithOffset.left as PtIdentifier).name
|
||||
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg), null)
|
||||
return result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
array != null -> {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(array!!.type, null)
|
||||
val variable = array!!.variable
|
||||
if(variable==null)
|
||||
translateRegularAssignPointerIndexed(result, array!!.pointerderef!!, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
|
||||
else if(variable.type.isPointer)
|
||||
assignToIndexedSimplePointer(result, variable, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
|
||||
else
|
||||
translateRegularAssignArrayIndexed(result, variable.name, eltSize, array!!, zero, targetDt, valueRegister, valueFpRegister)
|
||||
return result
|
||||
}
|
||||
pointerDeref != null -> {
|
||||
val (addressReg, offset) = codeGen.evaluatePointerAddressIntoReg(result, pointerDeref!!)
|
||||
val actualValueReg = if(pointerDeref!!.type.isFloat) valueFpRegister else valueRegister
|
||||
codeGen.storeValueAtPointersLocation(result, addressReg, offset, pointerDeref!!.type, zero, actualValueReg)
|
||||
return result
|
||||
|
||||
val tr = expressionEval.translateExpression(targetMemory.address)
|
||||
val addressReg = tr.resultReg
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg), null)
|
||||
return result
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird assigntarget")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
else if(targetPointerDeref!=null) {
|
||||
val (addressReg, offset) = codeGen.evaluatePointerAddressIntoReg(result, targetPointerDeref)
|
||||
val actualValueReg = if(targetPointerDeref.type.isFloat) valueFpRegister else valueRegister
|
||||
codeGen.storeValueAtPointersLocation(result, addressReg, offset, targetPointerDeref.type, zero, actualValueReg)
|
||||
return result
|
||||
}
|
||||
else
|
||||
throw AssemblyError("weird assigntarget")
|
||||
}
|
||||
|
||||
private fun assignToIndexedSimplePointer(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StMemorySlabBlockName
|
||||
import prog8.code.StStructInstanceBlockName
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
@@ -52,6 +54,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
|
||||
"prog8_lib_structalloc" -> funcStructAlloc(call)
|
||||
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
|
||||
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant")
|
||||
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||
}
|
||||
}
|
||||
@@ -500,7 +503,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val name = (call.args[0] as PtString).value
|
||||
val code = IRCodeChunk(null, null)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabPrefix.prog8_memoryslab_$name")
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabBlockName.memory_$name")
|
||||
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
@@ -508,7 +511,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val code = IRCodeChunk(null, null)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
val labelname = SymbolTable.labelnameForStructInstance(call)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = labelname)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname")
|
||||
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
|
||||
@@ -47,10 +47,16 @@ private fun convert(struct: StStruct): IRStStructDef =
|
||||
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
|
||||
|
||||
|
||||
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
|
||||
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
|
||||
else
|
||||
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
|
||||
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement {
|
||||
return if (elt.boolean != null)
|
||||
IRStArrayElement(elt.boolean, null, null)
|
||||
else if(elt.number!=null)
|
||||
IRStArrayElement(null, elt.number, null)
|
||||
else {
|
||||
val symbol = elt.addressOfSymbol ?: (StStructInstanceBlockName + "." + (elt.structInstance ?: elt.structInstanceUninitialized))
|
||||
IRStArrayElement(null, null, symbol)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun convert(variable: StStaticVariable): IRStStaticVariable {
|
||||
@@ -72,7 +78,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
|
||||
val newArray = mutableListOf<IRStArrayElement>()
|
||||
array.forEach {
|
||||
if(it.addressOfSymbol!=null) {
|
||||
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
|
||||
val target = variable.lookup(it.addressOfSymbol!!) ?:
|
||||
throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
|
||||
newArray.add(IRStArrayElement(null, null, target.scopedNameString))
|
||||
} else {
|
||||
newArray.add(convertArrayElt(it))
|
||||
@@ -129,11 +136,11 @@ private fun convert(constant: StConstant): IRStConstant {
|
||||
}
|
||||
|
||||
|
||||
private fun convert(variable: StMemorySlab): IRStMemorySlab {
|
||||
return if('.' in variable.name)
|
||||
IRStMemorySlab(variable.name, variable.size, variable.align)
|
||||
private fun convert(mem: StMemorySlab): IRStMemorySlab {
|
||||
return if('.' in mem.name)
|
||||
IRStMemorySlab(mem.name, mem.size, mem.align)
|
||||
else
|
||||
IRStMemorySlab("$StMemorySlabPrefix.${variable.name}", variable.size, variable.align)
|
||||
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
|
||||
}
|
||||
|
||||
|
||||
@@ -142,8 +149,8 @@ private fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType,
|
||||
val elt = convertArrayElt(value)
|
||||
IRStructInitValue(field.first.base, elt)
|
||||
}
|
||||
return IRStStructInstance(instance.name, instance.structName, values, instance.size)
|
||||
return if('.' in instance.name)
|
||||
IRStStructInstance(instance.name, instance.structName, values, instance.size)
|
||||
else
|
||||
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
|
||||
}
|
||||
|
||||
|
||||
internal const val StMemorySlabPrefix = "prog8_slabs" // TODO also add ".prog8_memoryslab_" ?
|
||||
@@ -85,6 +85,8 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
* (X + c1) - c2 -> X + (c1-c2)
|
||||
*/
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
|
||||
@@ -26,19 +26,15 @@ class VarConstantValueTypeAdjuster(
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(decl.parent is AnonymousScope)
|
||||
throw FatalAstException("vardecl may no longer occur in anonymousscope")
|
||||
throw FatalAstException("vardecl may no longer occur in anonymousscope ${decl.position}")
|
||||
|
||||
try {
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& declConstValue.type != decl.datatype.base) {
|
||||
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
|
||||
// avoid silent float roundings
|
||||
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
|
||||
}
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& declConstValue.type != decl.datatype.base) {
|
||||
if(decl.datatype.isInteger && declConstValue.type == BaseDataType.FLOAT) {
|
||||
// avoid silent float roundings
|
||||
errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position)
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
}
|
||||
|
||||
// replace variables by constants, if possible
|
||||
@@ -313,41 +309,36 @@ internal class ConstantIdentifierReplacer(
|
||||
if(!dt.isKnown || !dt.isNumeric && !dt.isBool)
|
||||
return noModifications
|
||||
|
||||
try {
|
||||
val cval = identifier.constValue(program) ?: return noModifications
|
||||
val arrayIdx = identifier.parent as? ArrayIndexedExpression
|
||||
if(arrayIdx!=null && cval.type.isNumeric) {
|
||||
// special case when the identifier is used as a pointer var
|
||||
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
|
||||
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
|
||||
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
|
||||
return if(arrayIdx.parent is AssignTarget) {
|
||||
val memwrite = DirectMemoryWrite(add, identifier.position)
|
||||
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
|
||||
} else {
|
||||
val memread = DirectMemoryRead(add, identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
|
||||
}
|
||||
val cval = identifier.constValue(program) ?: return noModifications
|
||||
val arrayIdx = identifier.parent as? ArrayIndexedExpression
|
||||
if(arrayIdx!=null && cval.type.isNumeric) {
|
||||
// special case when the identifier is used as a pointer var
|
||||
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
|
||||
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
|
||||
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
|
||||
return if(arrayIdx.parent is AssignTarget) {
|
||||
val memwrite = DirectMemoryWrite(add, identifier.position)
|
||||
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
|
||||
} else {
|
||||
val memread = DirectMemoryRead(add, identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
|
||||
}
|
||||
when {
|
||||
cval.type.isNumericOrBool -> {
|
||||
if(parent is AddressOf)
|
||||
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
NumericLiteral(cval.type, cval.number, identifier.position),
|
||||
identifier.parent
|
||||
)
|
||||
}
|
||||
when {
|
||||
cval.type.isNumericOrBool -> {
|
||||
if(parent is AddressOf)
|
||||
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
identifier,
|
||||
NumericLiteral(cval.type, cval.number, identifier.position),
|
||||
identifier.parent
|
||||
)
|
||||
}
|
||||
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
|
||||
else -> return noModifications
|
||||
)
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
return noModifications
|
||||
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.log2
|
||||
|
||||
|
||||
@@ -90,6 +91,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
val newExpr = applyAbsorptionLaws(expr)
|
||||
if(newExpr!=null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
@@ -424,19 +427,92 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
}
|
||||
|
||||
// fun isPowerOfTwo(number: Double): Boolean {
|
||||
// val intValue = number.toInt()
|
||||
// return intValue > 0 && (intValue and (intValue - 1)) == 0
|
||||
// }
|
||||
|
||||
fun isFactorOf256(number: Double): Boolean {
|
||||
val intValue = number.toInt()
|
||||
return intValue >= 256 && (intValue and 0xFF) == 0
|
||||
}
|
||||
|
||||
if (leftDt.isUnsignedWord && rightVal!=null) {
|
||||
if (expr.operator == ">" && rightVal.number == 255.0 || expr.operator == ">=" && rightVal.number == 256.0) {
|
||||
// uword > 255 --> msb(value)!=0
|
||||
// uword >= 256 --> msb(value)!=0
|
||||
expr.operator = "!="
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator==">=" && isFactorOf256(rightVal.number)) {
|
||||
// uword >= $xx00 --> msb(value)>=xx
|
||||
val msbFraction = floor(rightVal.number / 256.0)
|
||||
expr.operator = ">="
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator==">" && isFactorOf256(rightVal.number+1)) {
|
||||
// uword > $xxFF --> msb(value)>xx
|
||||
val msbFraction = floor((rightVal.number) / 256.0)
|
||||
expr.operator = ">"
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator == "<" && rightVal.number == 256.0 || expr.operator == "<=" && rightVal.number == 255.0) {
|
||||
// uword < 256 --> msb(value)==0
|
||||
// uword <= 255 --> msb(value)==0
|
||||
expr.operator = "=="
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, 0.0, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator == "<" && isFactorOf256(rightVal.number)) {
|
||||
// uword < $xx00 --> msb(value)<xx
|
||||
val msbFraction = floor(rightVal.number / 256.0)
|
||||
expr.operator = "<"
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
else if(expr.operator=="<=" && isFactorOf256(rightVal.number+1)) {
|
||||
// uword <= $xxFF --> msb(value)<=xx
|
||||
val msbFraction = floor((rightVal.number) / 256.0)
|
||||
expr.operator = "<="
|
||||
expr.left = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.left.position), mutableListOf(expr.left), expr.left.position)
|
||||
expr.right = NumericLiteral(BaseDataType.UBYTE, msbFraction, expr.right.position)
|
||||
expr.linkParents(parent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(arrayIndexedExpression.indexer.constIndex()==0) {
|
||||
if(arrayIndexedExpression.plainarrayvar!=null) {
|
||||
if((arrayIndexedExpression.parent as? BinaryExpression)?.operator !=".") {
|
||||
val binexprParent = arrayIndexedExpression.parent as? BinaryExpression
|
||||
if(binexprParent?.operator!=".") {
|
||||
val dt = arrayIndexedExpression.plainarrayvar!!.inferType(program).getOrUndef()
|
||||
if(dt.isPointer) {
|
||||
// pointer[0] --> pointer^^
|
||||
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource, true, arrayIndexedExpression.plainarrayvar!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression,deref, parent))
|
||||
}
|
||||
} else if(arrayIndexedExpression.pointerderef==null) {
|
||||
// possibly pointer[0].field --> pointer.field
|
||||
val target = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
|
||||
if(target?.datatype?.isPointer==true) {
|
||||
val field = (binexprParent.right as? IdentifierReference)?.nameInSource
|
||||
if(field!=null) {
|
||||
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource + field, false, arrayIndexedExpression.plainarrayvar!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression.parent, deref, arrayIndexedExpression.parent.parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val ptrDeref = arrayIndexedExpression.pointerderef
|
||||
|
||||
@@ -8,7 +8,7 @@ import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.PROG8_CONTAINER_MODULES
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.compiler.CallGraph
|
||||
@@ -93,7 +93,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars) {
|
||||
if (block.name != INTERNED_STRINGS_MODULENAME && "ignore_unused" !in block.options()) {
|
||||
if (block.name !in PROG8_CONTAINER_MODULES && "ignore_unused" !in block.options()) {
|
||||
if (!block.statements.any { it is Subroutine && it.hasBeenInlined })
|
||||
errors.info("removing unused block '${block.name}'", block.position)
|
||||
}
|
||||
|
||||
@@ -506,6 +506,7 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
@@ -513,7 +514,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
_irq_handler
|
||||
jsr sys.save_prog8_internals
|
||||
@@ -545,6 +546,7 @@ _vector .word ?
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
lda #<cbm.IRQDFRT
|
||||
sta cbm.CINV
|
||||
@@ -554,24 +556,34 @@ asmsub restore_irq() clobbers(A) {
|
||||
sta c64.IREQMASK ; enable raster irq
|
||||
lda #%10000001
|
||||
sta c64.CIA1ICR ; restore CIA1 irq
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
sta user_vector
|
||||
sty user_vector+1
|
||||
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr _setup_raster_irq
|
||||
jsr sys.set_rasterline
|
||||
lda #%00000001
|
||||
sta c64.IREQMASK ; enable raster interrupt signals from vic
|
||||
|
||||
lda #<_raster_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
@@ -593,34 +605,46 @@ _raster_irq_handler
|
||||
pla
|
||||
rti
|
||||
_run_custom
|
||||
jmp (_vector)
|
||||
jmp (user_vector)
|
||||
.section BSS
|
||||
_vector .word ?
|
||||
user_vector .word ?
|
||||
.send BSS
|
||||
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
and c64.SCROLY
|
||||
sta c64.SCROLY ; clear most significant bit of raster position
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
pla
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
cpy #0
|
||||
beq +
|
||||
lda c64.SCROLY
|
||||
ora #%10000000
|
||||
sta c64.SCROLY ; set most significant bit of raster position
|
||||
+ lda #%00000001
|
||||
sta c64.IREQMASK ; enable raster interrupt signals from vic
|
||||
rts
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
; -- just update the IRQ handler and raster line position for the raster IRQ
|
||||
; this is much more efficient than calling set_rasterirq() again every time.
|
||||
; (but you have to call that one initially at least once to setup the prog8 handler itself)
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta sys.set_rasterirq.user_vector
|
||||
sty sys.set_rasterirq.user_vector+1
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
jsr sys.set_rasterline
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterline(uword line @AY) {
|
||||
; -- only set a new raster line for the raster IRQ
|
||||
%asm {{
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
lda c64.SCROLY
|
||||
and #%01111111
|
||||
cpy #0
|
||||
beq +
|
||||
ora #%10000000
|
||||
+ sta c64.SCROLY ; clear most significant bit of raster position
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
@@ -680,6 +704,26 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitrasterline(uword line @AY) {
|
||||
; -- CPU busy wait until the given raster line is reached
|
||||
%asm {{
|
||||
cpy #0
|
||||
bne _larger
|
||||
- cmp c64.RASTER
|
||||
bne -
|
||||
bit c64.SCROLY
|
||||
bmi -
|
||||
rts
|
||||
_larger
|
||||
cmp c64.RASTER
|
||||
bne _larger
|
||||
bit c64.SCROLY
|
||||
bpl _larger
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
|
||||
@@ -313,12 +313,13 @@ asmsub banks(ubyte banks @A) {
|
||||
%asm {{
|
||||
and #%00000111
|
||||
sta P8ZP_SCRATCH_REG
|
||||
php
|
||||
sei
|
||||
lda $01
|
||||
and #%11111000
|
||||
ora P8ZP_SCRATCH_REG
|
||||
sta $01
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@@ -511,6 +512,7 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
@@ -518,7 +520,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
_irq_handler
|
||||
jsr sys.save_prog8_internals
|
||||
@@ -551,6 +553,7 @@ _vector .word ?
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
lda #<cbm.IRQDFRT
|
||||
sta cbm.CINV
|
||||
@@ -560,24 +563,34 @@ asmsub restore_irq() clobbers(A) {
|
||||
sta c64.IREQMASK ; disable raster irq
|
||||
lda #%10000001
|
||||
sta c64.CIA1ICR ; restore CIA1 irq
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
sta user_vector
|
||||
sty user_vector+1
|
||||
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr _setup_raster_irq
|
||||
jsr sys.set_rasterline
|
||||
lda #%00000001
|
||||
sta c64.IREQMASK ; enable raster interrupt signals from vic
|
||||
|
||||
lda #<_raster_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
@@ -600,33 +613,46 @@ _raster_irq_handler
|
||||
rti
|
||||
|
||||
_run_custom
|
||||
jmp (_vector)
|
||||
jmp (user_vector)
|
||||
.section BSS
|
||||
_vector .word ?
|
||||
user_vector .word ?
|
||||
.send BSS
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
and c64.SCROLY
|
||||
sta c64.SCROLY ; clear most significant bit of raster position
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
pla
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
cpy #0
|
||||
beq +
|
||||
lda c64.SCROLY
|
||||
ora #%10000000
|
||||
sta c64.SCROLY ; set most significant bit of raster position
|
||||
+ lda #%00000001
|
||||
sta c64.IREQMASK ; enable raster interrupt signals from vic
|
||||
rts
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
; -- just update the IRQ handler and raster line position for the raster IRQ
|
||||
; this is much more efficient than calling set_rasterirq() again every time.
|
||||
; (but you have to call that one initially at least once to setup the prog8 handler itself)
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta sys.set_rasterirq.user_vector
|
||||
sty sys.set_rasterirq.user_vector+1
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
jsr sys.set_rasterline
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterline(uword line @AY) {
|
||||
; -- only set a new raster line for the raster IRQ
|
||||
%asm {{
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
lda c64.SCROLY
|
||||
and #%01111111
|
||||
cpy #0
|
||||
beq +
|
||||
ora #%10000000
|
||||
+ sta c64.SCROLY ; clear most significant bit of raster position
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
@@ -687,6 +713,26 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitrasterline(uword line @AY) {
|
||||
; -- CPU busy wait until the given raster line is reached
|
||||
%asm {{
|
||||
cpy #0
|
||||
bne _larger
|
||||
- cmp c64.RASTER
|
||||
bne -
|
||||
bit c64.SCROLY
|
||||
bmi -
|
||||
rts
|
||||
_larger
|
||||
cmp c64.RASTER
|
||||
bne _larger
|
||||
bit c64.SCROLY
|
||||
bpl _larger
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
|
||||
@@ -283,7 +283,7 @@ palette {
|
||||
; NOTE: this routine requires rom version 49+
|
||||
alias set_default = cx16.set_default_palette
|
||||
|
||||
; get the bank and address of the word-array containing the 256 default palette colors
|
||||
; get the bank and address (in A, and XY) of the word-array containing the 256 default palette colors
|
||||
; NOTE: this routine requires rom version 49+
|
||||
alias get_default = cx16.get_default_palette
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
; Simple routines to control sprites.
|
||||
|
||||
; They're not written for high performance, but for simplicity.
|
||||
; That's why they control 1 sprite at a time. \
|
||||
; That's why they control 1 sprite at a time.
|
||||
; The exceptions are pos_batch() and pos_batch_split(); these are quite efficient
|
||||
; to update sprite positions of multiple sprites in one call.
|
||||
|
||||
; HIGH PERFORMANCE sprite handling would probably have a copy of the sprite registers for each sprite instead,
|
||||
; and a unrolled loop that copies those into the VERA registers when needed.
|
||||
|
||||
; note: sprites z-order will be in front of all layers.
|
||||
; note: collision mask is not supported here yet.
|
||||
; note: "palette offset" is counted as 0-15 (vera multiplies the offset by 16 to get at the actual color index)
|
||||
|
||||
@@ -1576,6 +1576,7 @@ sys {
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
; Sets the handler for the VSYNC interrupt, and enable that interrupt.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
@@ -1585,7 +1586,7 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
sta cbm.CINV+1
|
||||
lda #1
|
||||
tsb cx16.VERA_IEN ; enable the vsync irq
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
|
||||
_irq_handler
|
||||
@@ -1615,6 +1616,7 @@ _vector .word ?
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
lda _orig_irqvec
|
||||
sta cbm.CINV
|
||||
@@ -1624,7 +1626,7 @@ asmsub restore_irq() clobbers(A) {
|
||||
and #%11110000 ; disable all Vera IRQs but the vsync
|
||||
ora #%00000001
|
||||
sta cx16.VERA_IEN
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
.section BSS_NOCLEAR
|
||||
_orig_irqvec .word ?
|
||||
@@ -1636,9 +1638,10 @@ _orig_irqvec .word ?
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
; Sets the handler for the LINE interrupt, and enable (only) that interrupt.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
sta user_vector
|
||||
sty user_vector+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
lda cx16.VERA_IEN
|
||||
@@ -1652,7 +1655,7 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
sta cbm.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta cbm.CINV+1
|
||||
cli
|
||||
plp
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
@@ -1668,15 +1671,32 @@ _raster_irq_handler
|
||||
pla
|
||||
rti
|
||||
_run_custom
|
||||
jmp (_vector)
|
||||
jmp (user_vector)
|
||||
.section BSS
|
||||
_vector .word ?
|
||||
user_vector .word ?
|
||||
.send BSS
|
||||
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub update_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
|
||||
; -- just update the IRQ handler and raster line position for the raster IRQ
|
||||
; this is much more efficient than calling set_rasterirq() again every time.
|
||||
; (but you have to call that one initially at least once to setup the prog8 handler itself)
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta sys.set_rasterirq.user_vector
|
||||
sty sys.set_rasterirq.user_vector+1
|
||||
lda cx16.r0L
|
||||
ldy cx16.r0H
|
||||
jsr set_rasterline
|
||||
plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterline(uword line @AY) {
|
||||
%asm {{
|
||||
php
|
||||
@@ -1732,13 +1752,15 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
rts
|
||||
|
||||
+ sei
|
||||
+ php
|
||||
sei
|
||||
jsr cbm.RDTIM
|
||||
cli
|
||||
plp
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- sei
|
||||
- php
|
||||
sei
|
||||
jsr cbm.RDTIM
|
||||
cli
|
||||
plp
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq -
|
||||
|
||||
@@ -1759,6 +1781,26 @@ _loop lda P8ZP_SCRATCH_W1
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitrasterline(uword line @AY) {
|
||||
; -- CPU busy wait until the given raster line is reached
|
||||
%asm {{
|
||||
cpy #0
|
||||
bne _larger
|
||||
- cmp cx16.VERA_SCANLINE_L
|
||||
bne -
|
||||
bit cx16.VERA_IEN
|
||||
bvs -
|
||||
rts
|
||||
_larger
|
||||
cmp cx16.VERA_SCANLINE_L
|
||||
bne _larger
|
||||
bit cx16.VERA_IEN
|
||||
bvc _larger
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub internal_stringcopy(str source @R0, str target @AY) clobbers (A,Y) {
|
||||
; Called when the compiler wants to assign a string value to another string.
|
||||
%asm {{
|
||||
|
||||
@@ -5,6 +5,7 @@ import prog8.ast.FatalAstException
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.SyntaxError
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.StructDecl
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.*
|
||||
@@ -15,6 +16,7 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
|
||||
"abs" to ::builtinAbs,
|
||||
"len" to ::builtinLen,
|
||||
"sizeof" to ::builtinSizeof,
|
||||
"offsetof" to ::builtinOffsetof,
|
||||
"sgn" to ::builtinSgn,
|
||||
"sqrt__ubyte" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
|
||||
"sqrt__uword" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
|
||||
@@ -90,6 +92,25 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
|
||||
else throw SyntaxError("abs requires one integer argument", position)
|
||||
}
|
||||
|
||||
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
// 1 arg, "Struct.field"
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("offsetof requires one argument", position)
|
||||
val identifier = (args[0] as? IdentifierReference)?.nameInSource
|
||||
if(identifier==null || identifier.size<2)
|
||||
throw CannotEvaluateException("offsetof","argument should be an identifier of the form Struct.field")
|
||||
|
||||
val structname = identifier.dropLast(1)
|
||||
val fieldname = identifier.last()
|
||||
val struct = args[0].definingScope.lookup(structname) as? StructDecl
|
||||
if(struct==null)
|
||||
throw SyntaxError("cannot find struct '$structname'", args[0].position)
|
||||
val offset = struct.offsetof(fieldname, program.memsizer)
|
||||
if(offset==null)
|
||||
throw SyntaxError("no such field '${identifier.joinToString(".")}'", args[0].position)
|
||||
return NumericLiteral.optimalInteger(offset.toInt(), position)
|
||||
}
|
||||
|
||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
// 1 arg, type = anything, result type = ubyte or uword
|
||||
if(args.size!=1)
|
||||
|
||||
@@ -5,6 +5,7 @@ import prog8.ast.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.ast.printAst
|
||||
@@ -176,17 +177,21 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
println("*********** COMPILER AST END *************\n")
|
||||
}
|
||||
|
||||
var symbolTable: SymbolTable
|
||||
|
||||
val (intermediateAst, simplifiedAstDuration2) = measureTimedValue {
|
||||
val intermediateAst = SimplifiedAstMaker(program, args.errors).transform()
|
||||
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
|
||||
val symbolTable = stMaker.make()
|
||||
symbolTable = stMaker.make()
|
||||
|
||||
postprocessSimplifiedAst(intermediateAst, symbolTable, compilationOptions, args.errors)
|
||||
args.errors.report()
|
||||
symbolTable = stMaker.make() // need an updated ST because the postprocessing changes stuff
|
||||
|
||||
if (compilationOptions.optimize) {
|
||||
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors)
|
||||
args.errors.report()
|
||||
symbolTable = stMaker.make() // need an updated ST because the optimization changes stuff
|
||||
}
|
||||
|
||||
if (args.printAst2) {
|
||||
@@ -204,6 +209,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
createAssemblyDuration = measureTime {
|
||||
if (!createAssemblyAndAssemble(
|
||||
intermediateAst,
|
||||
symbolTable,
|
||||
args.errors,
|
||||
compilationOptions,
|
||||
program.generatedLabelSequenceNumber
|
||||
@@ -558,6 +564,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
||||
}
|
||||
|
||||
private fun createAssemblyAndAssemble(program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
errors: IErrorReporter,
|
||||
compilerOptions: CompilationOptions,
|
||||
lastGeneratedLabelSequenceNr: Int
|
||||
@@ -572,10 +579,6 @@ private fun createAssemblyAndAssemble(program: PtProgram,
|
||||
else
|
||||
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.cpu}")
|
||||
|
||||
// need to make a new symboltable here to capture possible changes made by optimization steps performed earlier!
|
||||
val stMaker = SymbolTableMaker(program, compilerOptions)
|
||||
val symbolTable = stMaker.make()
|
||||
|
||||
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
|
||||
errors.report()
|
||||
|
||||
|
||||
@@ -1092,7 +1092,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if(decl.datatype.isStructInstance && decl.origin!=VarDeclOrigin.SUBROUTINEPARAM) {
|
||||
if(decl.type==VarDeclType.MEMORY)
|
||||
errors.err("cannot declare memory mapped struct instances, use a pointer and memory allocation call or direct address assignment instead", decl.position)
|
||||
errors.err("struct instances cannot be declared as memory-mapped currently, use a pointer and memory allocation call or direct address assignment instead", decl.position)
|
||||
else
|
||||
errors.err("struct instances cannot be declared directly, use a pointer and memory allocation call or direct address assignment instead", decl.position)
|
||||
}
|
||||
@@ -1269,17 +1269,23 @@ internal class AstChecker(private val program: Program,
|
||||
val arrayspec = ArrayIndex.forArray(array)
|
||||
checkValueTypeAndRangeArray(array.type.getOrUndef(), arrayspec, array)
|
||||
} else {
|
||||
errors.err("undefined array type (multiple element types?)", array.position)
|
||||
errors.err("undefined array type (multiple or incompatible element types?)", array.position)
|
||||
}
|
||||
|
||||
if(array.parent is VarDecl) {
|
||||
if (!array.value.all { it is NumericLiteral || it is AddressOf || (it is FunctionCallExpression && it.target.targetStructDecl()!=null) }) {
|
||||
if (!array.value.all { it is NumericLiteral || it is AddressOf || it is StaticStructInitializer }) {
|
||||
errors.err("initialization value contains non-constant elements", array.value[0].position)
|
||||
}
|
||||
|
||||
if(array.value.any { it is FunctionCallExpression }) {
|
||||
errors.err("it is not yet possible to use struct initializations in an array, you have to do it one by one for now", array.value[0].position)
|
||||
// TODO this is because later in the simplified AST the allocate struct variable is still missing somehow
|
||||
val elementDt = (array.parent as VarDecl).datatype.elementType()
|
||||
if(elementDt.isPointer) {
|
||||
// all elements in the initializer array should be of the same element type
|
||||
array.value.forEach {
|
||||
val valueDt = it.inferType(program).getOrUndef()
|
||||
if(!valueDt.isUnsignedWord && valueDt != elementDt) {
|
||||
errors.err("struct initializer element has invalid type, expected $elementDt or uword but got $valueDt", it.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if(array.parent is ForLoop) {
|
||||
@@ -1898,34 +1904,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(target is StructDecl) {
|
||||
// it's a static struct inializer, check the values
|
||||
if(args.isNotEmpty()) {
|
||||
args.forEach {
|
||||
if(it is IdentifierReference) {
|
||||
val target = it.targetVarDecl()
|
||||
if(target!=null && target.datatype.isPointer) {
|
||||
errors.err("a pointer variable cannot be used in a static initialization value because its value is only known at runtime (use 0 here, and assign it later manually)", it.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!args.all { it is NumericLiteral || it is AddressOf || (it is TypecastExpression && it.expression is NumericLiteral)})
|
||||
errors.err("initialization value contains non-constant elements", args[0].position)
|
||||
if (target.fields.size != args.size)
|
||||
errors.err("initialization value needs to have same number of values as the struct has fields, or be empty: expected ${target.fields.size} or 0, got ${args.size}", args[0].position)
|
||||
else
|
||||
target.fields.zip(args).withIndex().forEach { (index, fv) ->
|
||||
val (field, value) = fv
|
||||
val valueDt = value.inferType(program)
|
||||
if(valueDt isNotAssignableTo field.first) {
|
||||
errors.err("value #${index+1} has incompatible type $valueDt for field '${field.second}' (${field.first})", value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO rest?
|
||||
}
|
||||
|
||||
args.forEach{
|
||||
checkLongType(it)
|
||||
if(it.inferType(program).isStructInstance)
|
||||
@@ -1974,7 +1952,10 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
}
|
||||
} else if(target!=null) {
|
||||
throw FatalAstException("target is not a variable")
|
||||
if(target is Subroutine || target is Label )
|
||||
errors.err("cannot array index a subroutine or label directly, use an intermediate uword pointer = &symbol instead", arrayIndexedExpression.position)
|
||||
else
|
||||
errors.err("indexed symbol is not a suitable variable", arrayIndexedExpression.position)
|
||||
}
|
||||
|
||||
if(arrayIndexedExpression.pointerderef!=null) {
|
||||
@@ -2179,6 +2160,8 @@ internal class AstChecker(private val program: Program,
|
||||
else
|
||||
errors.err("unable to determine type of dereferenced pointer expression", deref.position)
|
||||
}
|
||||
|
||||
super.visit(deref)
|
||||
}
|
||||
|
||||
private fun checkLongType(expression: Expression) {
|
||||
@@ -2282,6 +2265,20 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
return err("invalid word array initialization value ${value.type}, expected $targetDt")
|
||||
}
|
||||
targetDt.isPointerArray -> {
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
val maxLength = if(targetDt.isSplitWordArray) 256 else 128
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize>maxLength)
|
||||
return err("array length must be 1-$maxLength")
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
return true
|
||||
}
|
||||
return err("invalid array size, must be 1-$maxLength")
|
||||
}
|
||||
targetDt.isFloatArray -> {
|
||||
// value may be either a single float, or a float arraysize
|
||||
if(value.type istype targetDt) {
|
||||
@@ -2396,6 +2393,7 @@ internal class AstChecker(private val program: Program,
|
||||
else
|
||||
cast.valueOrZero().number
|
||||
}
|
||||
is StaticStructInitializer -> it.structname.hashCode() and 0xffff
|
||||
else -> -9999999
|
||||
}
|
||||
}
|
||||
@@ -2420,7 +2418,7 @@ internal class AstChecker(private val program: Program,
|
||||
else -> throw FatalAstException("invalid type $targetDt")
|
||||
}
|
||||
if (!correct) {
|
||||
if (value.parent is VarDecl && !value.value.all { it is NumericLiteral || it is AddressOf })
|
||||
if (value.parent is VarDecl && !value.value.all { it is NumericLiteral || it is AddressOf || it is StaticStructInitializer })
|
||||
errors.err("initialization value contains non-constant elements", value.value[0].position)
|
||||
else
|
||||
errors.err("array element out of range for type $targetDt", value.position)
|
||||
@@ -2481,7 +2479,7 @@ internal class AstChecker(private val program: Program,
|
||||
else if (targetDatatype.isPointer) {
|
||||
if(sourceDatatype.isPointer) {
|
||||
if(!(sourceDatatype isAssignableTo targetDatatype))
|
||||
errors.err("cannot assign different pointer type, expected $targetDatatype got $sourceDatatype", position)
|
||||
errors.err("cannot assign different pointer type, expected $targetDatatype or uword but got $sourceDatatype", position)
|
||||
} else if(sourceDatatype.isString && targetDatatype.sub?.isByte==true) {
|
||||
// assigning a string to a byte pointer is allowed.
|
||||
} else if(!sourceDatatype.isUnsignedWord && !sourceDatatype.isStructInstance)
|
||||
@@ -2506,17 +2504,47 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(onGoto: OnGoto) {
|
||||
val t = onGoto.index.inferType(program)
|
||||
if(!onGoto.index.inferType(program).getOrUndef().isUnsignedByte) {
|
||||
errors.err("on..goto index must be an unsigned byte", onGoto.index.position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(deref: ArrayIndexedPtrDereference) {
|
||||
if(deref.parent is AssignTarget)
|
||||
errors.err("no support for assigning to a array indexed pointer target like this yet. Split the assignment statement by using an intermediate variable.", deref.position)
|
||||
else
|
||||
if(deref.parent !is AssignTarget)
|
||||
errors.err("no support for getting the target value of pointer array indexing like this yet. Split the expression by using an intermediate variable.", deref.position) // this may never occur anymore since more ArrayIndexedPtrDereference got rewritten
|
||||
|
||||
super.visit(deref)
|
||||
}
|
||||
|
||||
override fun visit(initializer: StaticStructInitializer) {
|
||||
val args = initializer.args
|
||||
if(args.isNotEmpty()) {
|
||||
args.forEach {
|
||||
if(it is IdentifierReference) {
|
||||
val target = it.targetVarDecl()
|
||||
if(target!=null && target.datatype.isPointer) {
|
||||
errors.err("a pointer variable cannot be used in a static initialization value because its value is only known at runtime (use 0 here, and assign it later manually)", it.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!args.all { it is NumericLiteral || it is AddressOf || (it is TypecastExpression && it.expression is NumericLiteral)})
|
||||
errors.err("initialization value contains non-constant elements", args[0].position)
|
||||
val struct = initializer.structname.targetStructDecl()
|
||||
if(struct!=null) {
|
||||
require(args.size==struct.fields.size)
|
||||
struct.fields.zip(args).withIndex().forEach { (index, fv) ->
|
||||
val (field, value) = fv
|
||||
val valueDt = value.inferType(program)
|
||||
if(valueDt isNotAssignableTo field.first)
|
||||
errors.err("value #${index+1} has incompatible type $valueDt for field '${field.second}' (${field.first})", value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(initializer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, errors: IErrorReporter) {
|
||||
|
||||
@@ -165,14 +165,23 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) = visitFunctionCall(functionCallExpr)
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
visitFunctionCall(functionCallExpr)
|
||||
super.visit(functionCallExpr)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
visitFunctionCall(functionCallStatement)
|
||||
super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun visit(initializer: StaticStructInitializer) {
|
||||
val fields = initializer.structname.targetStructDecl()!!.fields
|
||||
if(initializer.args.isNotEmpty() && initializer.args.size != fields.size) {
|
||||
val pos = (if(initializer.args.any()) initializer.args[0] else initializer).position
|
||||
invalidNumberOfArgsError(pos, initializer.args.size, fields.map { it.second }, true)
|
||||
val struct = initializer.structname.targetStructDecl()
|
||||
if(struct!=null) {
|
||||
if (initializer.args.isNotEmpty() && initializer.args.size != struct.fields.size) {
|
||||
val pos = (if (initializer.args.any()) initializer.args[0] else initializer).position
|
||||
invalidNumberOfArgsError(pos, initializer.args.size, struct.fields.map { it.second }, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,6 +174,8 @@ class AstPreprocessor(val program: Program,
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
if(expr.operator=="in") {
|
||||
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
||||
|
||||
@@ -153,6 +153,8 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
if (options.compTarget.name == VMTarget.NAME)
|
||||
return noModifications
|
||||
|
||||
|
||||
@@ -91,6 +91,8 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
if(expr.operator=="<<" || expr.operator==">>") {
|
||||
val shifts = expr.right.constValue(program)
|
||||
if(shifts!=null) {
|
||||
|
||||
@@ -605,11 +605,15 @@ _after:
|
||||
}
|
||||
|
||||
val replacementScope = AnonymousScope(if(conditionVar==null)
|
||||
mutableListOf(ifSt, jumplistArray)
|
||||
mutableListOf(ifSt)
|
||||
else
|
||||
mutableListOf(conditionVar, assignIndex!!, ifSt, jumplistArray)
|
||||
mutableListOf(conditionVar, assignIndex!!, ifSt)
|
||||
, ongoto.position)
|
||||
return listOf(IAstModification.ReplaceNode(ongoto, replacementScope, parent))
|
||||
val declscope = parent.definingScope
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ongoto, replacementScope, parent),
|
||||
IAstModification.InsertFirst(jumplistArray, declscope)
|
||||
)
|
||||
}
|
||||
|
||||
override fun after(deref: PtrDereference, parent: Node): Iterable<IAstModification> {
|
||||
@@ -658,6 +662,61 @@ _after:
|
||||
override fun after(deref: ArrayIndexedPtrDereference, parent: Node): Iterable<IAstModification> {
|
||||
// get rid of the ArrayIndexedPtrDereference AST node, replace it with other AST nodes that are equivalent
|
||||
|
||||
fun pokeFunc(dt: DataType): Pair<String, DataType?> {
|
||||
return when {
|
||||
dt.isBool -> "pokebool" to null
|
||||
dt.isUnsignedByte -> "poke" to null
|
||||
dt.isSignedByte -> "poke" to DataType.UBYTE
|
||||
dt.isUnsignedWord -> "pokew" to null
|
||||
dt.isSignedWord -> "pokew" to DataType.UWORD
|
||||
dt.isLong -> "pokel" to null
|
||||
dt.isFloat -> "pokef" to null
|
||||
else -> throw FatalAstException("can only deref a numeric or boolean pointer here")
|
||||
}
|
||||
}
|
||||
|
||||
if(parent is AssignTarget) {
|
||||
if(!deref.derefLast) {
|
||||
val assignment = parent.parent as Assignment
|
||||
val field = deref.chain.last()
|
||||
val ptr = deref.chain.dropLast(1)
|
||||
if(field.second==null && ptr.last().second!=null) {
|
||||
val ptrName = ptr.map { it.first }
|
||||
val ptrVar = deref.definingScope.lookup(ptrName) as? VarDecl
|
||||
if(ptrVar!=null && (ptrVar.datatype.isPointer || ptrVar.datatype.isPointerArray)) {
|
||||
val struct = ptrVar.datatype.subType!! as StructDecl
|
||||
val offsetNumber = NumericLiteral.optimalInteger(struct.offsetof(field.first, program.memsizer)!!.toInt(), deref.position)
|
||||
val pointerIdentifier = IdentifierReference(ptrName, deref.position)
|
||||
val address: Expression
|
||||
if(ptrVar.datatype.isPointer) {
|
||||
// pointer[idx].field = value --> pokeXXX(pointer as uword + idx*sizeof(Struct) + offsetof(Struct.field), value)
|
||||
val structSize = ptrVar.datatype.dereference().size(program.memsizer)
|
||||
val pointerAsUword = TypecastExpression(pointerIdentifier, DataType.UWORD, true, deref.position)
|
||||
val idx = ptr.last().second!!.indexExpr
|
||||
val scaledIndex = BinaryExpression(idx, "*", NumericLiteral(BaseDataType.UWORD, structSize.toDouble(), deref.position), deref.position)
|
||||
val structAddr = BinaryExpression(pointerAsUword, "+", scaledIndex, deref.position)
|
||||
address = BinaryExpression(structAddr, "+", offsetNumber, deref.position)
|
||||
}
|
||||
else {
|
||||
// pointerarray[idx].field = value --> pokeXXX(pointerarray[idx] as uword + offsetof(Struct.field), value)
|
||||
val index = ArrayIndexedExpression(pointerIdentifier, null, ptr.last().second!!, deref.position)
|
||||
val pointerAsUword = TypecastExpression(index, DataType.UWORD, true, deref.position)
|
||||
address = BinaryExpression(pointerAsUword, "+", offsetNumber, deref.position)
|
||||
}
|
||||
val (pokeFunc, valueCast) = pokeFunc(parent.inferType(program).getOrUndef())
|
||||
val value = if(valueCast==null) assignment.value else TypecastExpression(assignment.value, valueCast, true, assignment.value.position)
|
||||
val pokeCall = FunctionCallStatement(IdentifierReference(listOf(pokeFunc), assignment.position),
|
||||
mutableListOf(address, value), false, assignment.position)
|
||||
|
||||
if(assignment.isAugmentable)
|
||||
errors.warn("in-place assignment of indexed pointer variable currently is very inefficient, maybe use a temporary pointer variable", assignment.position)
|
||||
return listOf(IAstModification.ReplaceNode(assignment, pokeCall, assignment.parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(deref.chain.last().second!=null && deref.derefLast && deref.chain.dropLast(1).all { it.second==null } ) {
|
||||
|
||||
// parent could be Assigment directly, or a binexpr chained pointer expression (with '.' operator)
|
||||
@@ -710,15 +769,7 @@ _after:
|
||||
val dt = deref.inferType(program).getOrUndef()
|
||||
if(dt.isNumericOrBool) {
|
||||
// if it's something else beside number (like, a struct instance) we don't support rewriting that...
|
||||
val (pokeFunc, cast) =
|
||||
if (dt.isBool) "pokebool" to null
|
||||
else if (dt.isUnsignedByte) "poke" to null
|
||||
else if (dt.isSignedByte) "poke" to DataType.UBYTE
|
||||
else if (dt.isUnsignedWord) "pokew" to null
|
||||
else if (dt.isSignedWord) "pokew" to DataType.UWORD
|
||||
else if (dt.isLong) "pokel" to null
|
||||
else if (dt.isFloat) "pokef" to null
|
||||
else throw FatalAstException("can only deref a numeric or boolean pointer here")
|
||||
val (pokeFunc, cast) = pokeFunc(dt)
|
||||
val indexer = deref.chain.last().second!!
|
||||
val identifier = IdentifierReference(deref.chain.map { it.first }, deref.position)
|
||||
val indexed = ArrayIndexedExpression(identifier, null, indexer, deref.position)
|
||||
@@ -872,4 +923,68 @@ _after:
|
||||
errors.err(error, ifExpr.condition.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
fun convertArrayIntoStructInitializer(array: ArrayLiteral, struct: ISubType): StaticStructInitializer {
|
||||
val structname = IdentifierReference(struct.scopedNameString.split("."), array.position)
|
||||
return StaticStructInitializer(structname, array.value.toMutableList(), array.position)
|
||||
}
|
||||
|
||||
fun checkNumberOfElements(struct: StructDecl, array: ArrayLiteral): Boolean {
|
||||
val numValues = array.value.size
|
||||
if (numValues>0 && struct.fields.size != numValues) {
|
||||
if (numValues < struct.fields.size) {
|
||||
val missing = struct.fields.drop(numValues).joinToString(", ") { it.second }
|
||||
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got ${numValues}, missing: $missing", array.position)
|
||||
} else
|
||||
errors.err("invalid number of field values: expected ${struct.fields.size} or 0 but got ${numValues}", array.position)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if(parent is VarDecl) {
|
||||
if (parent.datatype.isPointerArray && parent.datatype.elementType().subType!=null) {
|
||||
val struct = parent.datatype.elementType().subType as StructDecl
|
||||
val allremovals = mutableListOf<VarDecl>()
|
||||
var noErrors = true
|
||||
var changes = false
|
||||
array.value.withIndex().forEach { (index, elt) ->
|
||||
if(elt is ArrayLiteral) {
|
||||
noErrors = noErrors and checkNumberOfElements(struct, elt)
|
||||
if(noErrors) {
|
||||
array.value[index] = convertArrayIntoStructInitializer(elt, struct)
|
||||
changes = true
|
||||
}
|
||||
} else if(elt is IdentifierReference) {
|
||||
val arrayvar = elt.targetVarDecl()!!.value as ArrayLiteral
|
||||
noErrors = noErrors and checkNumberOfElements(struct, arrayvar)
|
||||
if(noErrors) {
|
||||
array.value[index] = convertArrayIntoStructInitializer(arrayvar, struct)
|
||||
allremovals += elt.targetVarDecl()!!
|
||||
changes = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(changes && noErrors) {
|
||||
array.linkParents(parent)
|
||||
return allremovals.map { IAstModification.Remove(it, it.parent as IStatementContainer) }
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(parent is Assignment) {
|
||||
val targetDt = parent.target.inferType(program).getOrUndef()
|
||||
if(targetDt.isPointer && targetDt.subType!=null) {
|
||||
val struct = targetDt.subType as StructDecl
|
||||
if(checkNumberOfElements(struct, array)) {
|
||||
val initializser = convertArrayIntoStructInitializer(array, struct)
|
||||
return listOf(IAstModification.ReplaceNode(array, initializser, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
srcTarget.arrayindexed!=null -> target.add(transform(srcTarget.arrayindexed!!))
|
||||
srcTarget.memoryAddress!=null -> target.add(transform(srcTarget.memoryAddress!!))
|
||||
srcTarget.pointerDereference!=null -> target.add(transform(srcTarget.pointerDereference!!))
|
||||
srcTarget.arrayIndexedDereference!=null -> TODO("this array indexed dereference should have been converted to some other ast nodes ${srcTarget.position}")
|
||||
srcTarget.arrayIndexedDereference!=null -> throw FatalAstException("this should have been converted to some other ast nodes ${srcTarget.position}")
|
||||
!srcTarget.void -> throw FatalAstException("invalid AssignTarget")
|
||||
}
|
||||
return target
|
||||
|
||||
@@ -21,6 +21,8 @@ internal fun postprocessSimplifiedAst(
|
||||
private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable) {
|
||||
|
||||
fun getStStruct(subType: ISubType): StStruct {
|
||||
if(subType is StStruct)
|
||||
return subType
|
||||
val stNode = st.lookup(subType.scopedNameString) as? StStruct
|
||||
if(stNode != null)
|
||||
return stNode
|
||||
@@ -28,7 +30,7 @@ private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable)
|
||||
throw FatalAstException("cannot find in ST: ${subType.scopedNameString} $subType")
|
||||
}
|
||||
|
||||
fun fixSubtype(type: DataType) {
|
||||
fun fixSubtypeIntoStType(type: DataType) {
|
||||
if(type.subType!=null && type.subType !is StStruct) {
|
||||
type.subType = getStStruct(type.subType!!)
|
||||
}
|
||||
@@ -36,13 +38,21 @@ private fun processSubtypesIntoStReferences(program: PtProgram, st: SymbolTable)
|
||||
|
||||
fun fixSubtypes(node: PtNode) {
|
||||
when(node) {
|
||||
is IPtVariable -> fixSubtype(node.type)
|
||||
is PtPointerDeref -> fixSubtype(node.type)
|
||||
is PtStructDecl -> node.fields.forEach { fixSubtype(it.first) }
|
||||
is PtAsmSub -> node.returns.forEach { fixSubtype(it.second) }
|
||||
is PtExpression -> fixSubtype(node.type)
|
||||
is PtSubSignature -> node.returns.forEach { fixSubtype(it) }
|
||||
is PtSubroutineParameter -> fixSubtype(node.type)
|
||||
is IPtVariable -> {
|
||||
fixSubtypeIntoStType(node.type)
|
||||
// if it's an array, fix the subtypes of its elements as well
|
||||
if(node.type.isArray && node is PtVariable) {
|
||||
(node.value as? PtArray)?.let {array ->
|
||||
array.children.forEach { fixSubtypes(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtPointerDeref -> fixSubtypeIntoStType(node.type)
|
||||
is PtStructDecl -> node.fields.forEach { fixSubtypeIntoStType(it.first) }
|
||||
is PtAsmSub -> node.returns.forEach { fixSubtypeIntoStType(it.second) }
|
||||
is PtExpression -> fixSubtypeIntoStType(node.type)
|
||||
is PtSubSignature -> node.returns.forEach { fixSubtypeIntoStType(it) }
|
||||
is PtSubroutineParameter -> fixSubtypeIntoStType(node.type)
|
||||
else -> { /* has no datatype */ }
|
||||
}
|
||||
}
|
||||
@@ -175,7 +185,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
|
||||
is PtNumber,
|
||||
is PtRange,
|
||||
is PtString -> true
|
||||
is PtIdentifier -> true // actually PtIdentifier IS "complex" this time (it's a variable that might change) but it's kinda annoying to give a warning message for this very common case
|
||||
// note that PtIdentifier als is "complex" this time (it's a variable that might change)
|
||||
else -> false
|
||||
}
|
||||
|
||||
@@ -192,7 +202,6 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
|
||||
}
|
||||
|
||||
// complex return value, need to store it before calling the defer block
|
||||
errors.warn("using defer with nontrivial return value(s) incurs stack overhead", ret.children.first { !notComplex(it as PtExpression)}.position)
|
||||
val pushAndPopCalls = ret.children.map { makePushPopFunctionCalls(it as PtExpression) }
|
||||
val pushCalls = pushAndPopCalls.map { it.first }.reversed() // push in reverse order
|
||||
val popCalls = pushAndPopCalls.map { it.second }
|
||||
|
||||
@@ -384,4 +384,25 @@ internal class StatementReorderer(
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(assign, strcopy, assign.parent))
|
||||
}
|
||||
|
||||
override fun after(deref: ArrayIndexedPtrDereference, parent: Node): Iterable<IAstModification> {
|
||||
if(parent is AssignTarget) {
|
||||
val zeroIndexer = deref.chain.firstOrNull { it.second?.constIndex()==0 }
|
||||
if(zeroIndexer!=null) {
|
||||
val target = deref.definingScope.lookup(listOf(zeroIndexer.first))
|
||||
if(target is VarDecl && target.datatype.isPointer) {
|
||||
val position = deref.chain.indexOf(zeroIndexer)
|
||||
val rest = deref.chain.drop(position + 1)
|
||||
if (rest.size == 1 && rest[0].second == null) {
|
||||
// pointer[0]^^.field = xxx --> pointer^^.field = xxx
|
||||
val noindexer = zeroIndexer.first to null
|
||||
val newchain = deref.chain.take(position) + noindexer + rest
|
||||
val newDeref = PtrDereference(newchain.map { it.first }, false, deref.position)
|
||||
return listOf(IAstModification.ReplaceNode(deref, newDeref, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
val leftCv = expr.left.constValue(program)
|
||||
@@ -627,9 +630,12 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
return if(initializer.args.isEmpty())
|
||||
noModifications
|
||||
else {
|
||||
val struct = initializer.structname.targetStructDecl()!!
|
||||
val paramsPossibleDatatypes = struct.fields.map { listOf(it.first) }
|
||||
fixupArgumentList(paramsPossibleDatatypes, initializer.args, initializer)
|
||||
val struct = initializer.structname.targetStructDecl()
|
||||
if(struct!=null) {
|
||||
val paramsPossibleDatatypes = struct.fields.map { listOf(it.first) }
|
||||
fixupArgumentList(paramsPossibleDatatypes, initializer.args, initializer)
|
||||
}
|
||||
else noModifications
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,8 +124,12 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
return if(parent is IStatementContainer)
|
||||
listOf(ScopeFlatten(scope, parent as IStatementContainer))
|
||||
else
|
||||
else {
|
||||
if(scope.statements.any {it is VarDecl}) {
|
||||
throw FatalAstException("there are leftover vardecls in the nested scope at ${scope.position}, they should have been moved/placed in the declaration scope (subroutine) by now")
|
||||
}
|
||||
noModifications
|
||||
}
|
||||
}
|
||||
|
||||
private class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
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.SourceCode
|
||||
import prog8.compiler.ModuleImporter
|
||||
@@ -49,7 +50,7 @@ class TestModuleImporter: FunSpec({
|
||||
withClue(".file should point to specified path") {
|
||||
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
val error2 = importer.importMainModule(srcPathAbs).getErrorOrElse { error("should have import error") }
|
||||
withClue(".file should be normalized") {
|
||||
"${error2.file}" shouldBe "${error2.file.normalize()}"
|
||||
@@ -57,7 +58,7 @@ class TestModuleImporter: FunSpec({
|
||||
withClue(".file should point to specified path") {
|
||||
error2.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
}
|
||||
|
||||
test("testDirectory") {
|
||||
@@ -75,7 +76,7 @@ class TestModuleImporter: FunSpec({
|
||||
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
|
||||
shouldThrow<FileSystemException> { importer.importMainModule(srcPathAbs) }
|
||||
.let {
|
||||
@@ -86,7 +87,7 @@ class TestModuleImporter: FunSpec({
|
||||
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@ class TestModuleImporter: FunSpec({
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
|
||||
val module = importer.importMainModule(path.absolute()).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
}
|
||||
@@ -118,7 +119,7 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
|
||||
val module = importer.importMainModule(path).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
}
|
||||
@@ -131,7 +132,7 @@ class TestModuleImporter: FunSpec({
|
||||
assumeReadableFile(searchIn, path)
|
||||
|
||||
val module = importer.importMainModule(path).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size+1
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
}
|
||||
@@ -152,7 +153,7 @@ class TestModuleImporter: FunSpec({
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,9 +171,10 @@ class TestModuleImporter: FunSpec({
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 4 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
}
|
||||
}
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size }
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
|
||||
}
|
||||
}
|
||||
@@ -203,14 +205,14 @@ class TestModuleImporter: FunSpec({
|
||||
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
|
||||
errors.report()
|
||||
program.modules.size shouldBe 1
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
|
||||
val result2 = importer.importImplicitLibraryModule(filenameWithExt)
|
||||
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
|
||||
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
|
||||
errors.report()
|
||||
program.modules.size shouldBe 1
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +234,7 @@ class TestModuleImporter: FunSpec({
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +256,7 @@ class TestModuleImporter: FunSpec({
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
}
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe PROG8_CONTAINER_MODULES.size }
|
||||
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
|
||||
importer.errors.report()
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
"library/main",
|
||||
"plasma",
|
||||
"rasterbars",
|
||||
"simplemultiplexer",
|
||||
"sprites",
|
||||
"starfield",
|
||||
"tehtriz",
|
||||
@@ -149,6 +150,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"bdmusic",
|
||||
"bobs",
|
||||
"bubbleuniverse",
|
||||
"charfade",
|
||||
"charsets",
|
||||
"circles",
|
||||
"cobramk3-gfx",
|
||||
@@ -223,6 +225,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
"textelite",
|
||||
"pointers/animalgame",
|
||||
"pointers/binarytree",
|
||||
"pointers/hashtable",
|
||||
"pointers/sortedlist",
|
||||
"pointers/sorting"
|
||||
),
|
||||
@@ -253,6 +256,7 @@ class TestCompilerOnExamplesVirtual: FunSpec({
|
||||
"sincos",
|
||||
"pointers/animalgame",
|
||||
"pointers/binarytree",
|
||||
"pointers/hashtable",
|
||||
"pointers/sortedlist",
|
||||
"pointers/fountain-virtual",
|
||||
"pointers/sorting"
|
||||
|
||||
+194
-143
@@ -3,7 +3,6 @@ package prog8tests.compiler
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.comparables.shouldBeGreaterThanOrEqualTo
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
@@ -1027,118 +1026,6 @@ main {
|
||||
st[8] shouldBe instanceOf<Assignment>()
|
||||
}
|
||||
|
||||
test("indexing pointers with index 0 is just a direct pointer dereference except when followed by a struct field lookup") {
|
||||
val src="""
|
||||
%import floats
|
||||
main {
|
||||
struct List {
|
||||
^^uword s
|
||||
ubyte n
|
||||
}
|
||||
sub start() {
|
||||
^^List l1 = ^^List : []
|
||||
^^word @shared wptr
|
||||
^^float @shared fptr
|
||||
float f1,f2
|
||||
|
||||
cx16.r0 = l1.s^^
|
||||
cx16.r1 = l1^^.s^^
|
||||
cx16.r2 = l1.s^^
|
||||
cx16.r3 = l1.s[0]
|
||||
cx16.r4 = l1^^.s[0]
|
||||
|
||||
l1.s^^ = 4242
|
||||
l1^^.s^^ = 4242
|
||||
l1.s^^ = 4242
|
||||
l1.s[0] = 4242
|
||||
;; l1^^.s[0] = 4242 ; TODO fix parse syntax error
|
||||
|
||||
cx16.r0s = wptr[0]
|
||||
cx16.r1s = wptr^^
|
||||
wptr^^ = 4242
|
||||
wptr[0] = 4242
|
||||
|
||||
f1 = fptr^^
|
||||
f2 = fptr[0]
|
||||
fptr^^ = 1.234
|
||||
fptr[0] = 1.234
|
||||
|
||||
; not changed to dereference:
|
||||
cx16.r0L = l1[0].n
|
||||
cx16.r1L = l1[1].n
|
||||
}
|
||||
}"""
|
||||
|
||||
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false)!!
|
||||
val st = result.compilerAst.entrypoint.statements
|
||||
st.size shouldBe 30
|
||||
val dr0 = (st[10] as Assignment).value as PtrDereference
|
||||
val dr1 = (st[11] as Assignment).value as PtrDereference
|
||||
val dr2 = (st[12] as Assignment).value as PtrDereference
|
||||
val dr3 = (st[13] as Assignment).value as PtrDereference
|
||||
val dr4 = (st[14] as Assignment).value as PtrDereference
|
||||
|
||||
val dr5 = (st[15] as Assignment).target.pointerDereference!!
|
||||
val dr6 = (st[16] as Assignment).target.pointerDereference!!
|
||||
val dr7 = (st[17] as Assignment).target.pointerDereference!!
|
||||
val dr8 = (st[18] as Assignment).target.pointerDereference!!
|
||||
|
||||
val dr9 = (st[19] as Assignment).value as FunctionCallExpression
|
||||
val dr10 = (st[20] as Assignment).value as PtrDereference
|
||||
val dr11 = (st[21] as Assignment).target.pointerDereference!!
|
||||
(st[22] as FunctionCallStatement).target.nameInSource shouldBe listOf("pokew")
|
||||
|
||||
val dr13 = (st[23] as Assignment).value as PtrDereference
|
||||
((st[24] as Assignment).value as FunctionCallExpression).target.nameInSource shouldBe listOf("peekf")
|
||||
val dr15 = (st[25] as Assignment).target.pointerDereference!!
|
||||
(st[26] as FunctionCallStatement).target.nameInSource shouldBe listOf("pokef")
|
||||
|
||||
dr0.chain shouldBe listOf("l1", "s")
|
||||
dr0.derefLast shouldBe true
|
||||
dr1.chain shouldBe listOf("l1", "s")
|
||||
dr1.derefLast shouldBe true
|
||||
dr2.chain shouldBe listOf("l1", "s")
|
||||
dr2.derefLast shouldBe true
|
||||
dr3.chain shouldBe listOf("l1", "s")
|
||||
dr3.derefLast shouldBe true
|
||||
dr4.chain shouldBe listOf("l1", "s")
|
||||
dr4.derefLast shouldBe true
|
||||
|
||||
dr5.chain shouldBe listOf("l1", "s")
|
||||
dr5.derefLast shouldBe true
|
||||
dr6.chain shouldBe listOf("l1", "s")
|
||||
dr6.derefLast shouldBe true
|
||||
dr7.chain shouldBe listOf("l1", "s")
|
||||
dr7.derefLast shouldBe true
|
||||
dr8.chain shouldBe listOf("l1", "s")
|
||||
dr8.derefLast shouldBe true
|
||||
|
||||
dr9.target.nameInSource shouldBe listOf("peekw")
|
||||
dr10.chain shouldBe listOf("wptr")
|
||||
dr10.derefLast shouldBe true
|
||||
dr11.chain shouldBe listOf("wptr")
|
||||
dr11.derefLast shouldBe true
|
||||
|
||||
dr13.chain shouldBe listOf("fptr")
|
||||
dr13.derefLast shouldBe true
|
||||
dr15.chain shouldBe listOf("fptr")
|
||||
dr15.derefLast shouldBe true
|
||||
|
||||
val list0 = (st[27] as Assignment).value as BinaryExpression
|
||||
val list1 = (st[28] as Assignment).value as BinaryExpression
|
||||
|
||||
list0.operator shouldBe "."
|
||||
(list0.right as IdentifierReference).nameInSource shouldBe listOf("n")
|
||||
val list0left = list0.left as ArrayIndexedExpression
|
||||
list0left.plainarrayvar!!.nameInSource shouldBe listOf("l1")
|
||||
list0left.indexer.constIndex() shouldBe 0
|
||||
list1.operator shouldBe "."
|
||||
(list1.right as IdentifierReference).nameInSource shouldBe listOf("n")
|
||||
val list1left = list0.left as ArrayIndexedExpression
|
||||
list1left.plainarrayvar!!.nameInSource shouldBe listOf("l1")
|
||||
list1left.indexer.constIndex() shouldBe 0
|
||||
}
|
||||
|
||||
test("indexing pointers to structs") {
|
||||
val src="""
|
||||
%import floats
|
||||
@@ -1458,33 +1345,32 @@ other {
|
||||
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("a.b.c[i]^^.value = X where pointer is struct gives good error message") {
|
||||
test("support for assigning to indexed pointers") {
|
||||
val src="""
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
other.foo.listarray[3]^^.value = cx16.r0
|
||||
other.foo()
|
||||
}
|
||||
}
|
||||
sprptr[2]^^.y = 99
|
||||
pokew(sprptr as uword + (sizeof(Sprite) as uword)*2 + offsetof(Sprite.y), 99)
|
||||
sprptr[cx16.r0L]^^.y = 99
|
||||
pokew(sprptr as uword + (sizeof(Sprite) as uword)*cx16.r0L + offsetof(Sprite.y), 99)
|
||||
|
||||
other {
|
||||
sub foo() {
|
||||
struct List {
|
||||
bool b
|
||||
uword value
|
||||
}
|
||||
|
||||
^^List[10] listarray
|
||||
listarray[3]^^.value = cx16.r0
|
||||
listarray[3]^^ = 999 ; cannot assign word value to struct instance
|
||||
sprites[2]^^.y = 99
|
||||
pokew(sprites[2] as uword + offsetof(Sprite.y), 99)
|
||||
sprites[cx16.r0L]^^.y = 99
|
||||
pokew(sprites[cx16.r0L] as uword + offsetof(Sprite.y), 99)
|
||||
}
|
||||
|
||||
struct Sprite {
|
||||
ubyte x
|
||||
uword y
|
||||
}
|
||||
|
||||
^^Sprite[4] @shared sprites
|
||||
^^Sprite @shared sprptr
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBeGreaterThanOrEqualTo 3
|
||||
errors.errors[0] shouldContain "no support for"
|
||||
errors.errors[1] shouldContain "no support for"
|
||||
errors.errors[2] shouldContain "assigning this value to struct instance not supported"
|
||||
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
|
||||
compileText(C64Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
xtest("array indexed assignment parses with and without explicit dereference after struct pointer [IGNORED because it's a parser error right now]") {
|
||||
@@ -1731,7 +1617,34 @@ main {
|
||||
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("type error for invalid bool field initializer") {
|
||||
test("struct initializers in array") {
|
||||
val src="""
|
||||
%option enable_floats
|
||||
main {
|
||||
struct Node {
|
||||
ubyte id
|
||||
str name
|
||||
uword array
|
||||
bool flag
|
||||
float speed
|
||||
}
|
||||
|
||||
sub start() {
|
||||
^^Node[] @shared nodes = [
|
||||
^^Node:[1,"one", 1000, true, 1.111 ],
|
||||
^^Node:[2,"two", 2000, false, 2.222 ],
|
||||
^^Node:[3,"three", 3000, true, 3.333 ],
|
||||
^^Node:[],
|
||||
^^Node:[],
|
||||
^^Node:[],
|
||||
]
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, src, outputDir) shouldNotBe null
|
||||
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("type error for invalid field initializer") {
|
||||
val src="""
|
||||
main {
|
||||
struct Enemy {
|
||||
@@ -1743,9 +1656,9 @@ main {
|
||||
sub start() {
|
||||
^^Enemy @shared e1 = ^^Enemy: []
|
||||
^^Enemy @shared e2 = ^^Enemy: [1,2,3,true]
|
||||
^^Enemy @shared e3 = ^^Enemy: [1,2,3,4] ; TODO type error for the boolean
|
||||
^^Enemy @shared e4 = ^^Enemy: [1,2,3,4.555] ; TODO type error for the boolean
|
||||
|
||||
^^Enemy @shared e3 = ^^Enemy: [1,2,3,4]
|
||||
^^Enemy @shared e4 = ^^Enemy: [1,2,3,4.444]
|
||||
|
||||
e3.elite = 99
|
||||
e4.elite = 3.444
|
||||
}
|
||||
@@ -1753,9 +1666,78 @@ main {
|
||||
|
||||
val errors=ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "doesn't match target type"
|
||||
errors.errors[1] shouldContain "doesn't match target type"
|
||||
errors.errors.size shouldBe 4
|
||||
errors.errors[0] shouldContain "value #4 has incompatible type"
|
||||
errors.errors[1] shouldContain "value #4 has incompatible type"
|
||||
errors.errors[2] shouldContain "doesn't match target type"
|
||||
errors.errors[3] shouldContain "doesn't match target type"
|
||||
}
|
||||
|
||||
test("long and short form struct initializers") {
|
||||
val src="""
|
||||
%option enable_floats
|
||||
|
||||
main {
|
||||
struct Node {
|
||||
ubyte id
|
||||
str name
|
||||
uword array
|
||||
bool flag
|
||||
float perc
|
||||
}
|
||||
|
||||
sub start() {
|
||||
^^Node[] @shared nodeswithtype = [
|
||||
^^Node: [1,"one", 1000, true, 1.111],
|
||||
^^Node: [],
|
||||
]
|
||||
|
||||
^^Node[] @shared nodeswithout = [
|
||||
[2,"two", 2000, false, 2.222],
|
||||
[],
|
||||
]
|
||||
|
||||
^^Node @shared nptrwithtype = ^^Node : [1, "one", 1000, false, 3.333]
|
||||
^^Node @shared nptrwithouttype = [1, "one", 1000, false, 3.333]
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, src, outputDir) shouldNotBe null
|
||||
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("type error for wrong type in pointer array and assignment") {
|
||||
val src="""
|
||||
main {
|
||||
struct Node {
|
||||
ubyte id
|
||||
}
|
||||
struct Foobar {
|
||||
bool thing
|
||||
}
|
||||
|
||||
sub start() {
|
||||
^^Node[] onlynodes = [
|
||||
^^Node: [],
|
||||
^^Foobar: []
|
||||
]
|
||||
|
||||
uword multipleok = [
|
||||
^^Node: [],
|
||||
^^Foobar: []
|
||||
]
|
||||
|
||||
^^Node node = ^^Foobar: []
|
||||
}
|
||||
}"""
|
||||
val errors=ErrorReporterForTests()
|
||||
compileText(C64Target(), false, src, outputDir, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 6
|
||||
errors.errors[0] shouldContain "11:30: initialization value for pointer array"
|
||||
errors.errors[1] shouldContain "11:30: undefined array type" // a bit redundant but can't be helped
|
||||
errors.errors[2] shouldContain "13:13: struct initializer element has invalid type"
|
||||
errors.errors[3] shouldContain "16:28: invalid assignment value"
|
||||
errors.errors[4] shouldContain "16:28: undefined array type"
|
||||
errors.errors[5] shouldContain "21:23: cannot assign different pointer type"
|
||||
}
|
||||
|
||||
test("local and global struct pointer qualified name lookups") {
|
||||
@@ -1982,8 +1964,8 @@ main {
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors = errors, writeAssembly = false) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "7:24: cannot assign different pointer type, expected ^^uword got ^^ubyte"
|
||||
errors.errors[1] shouldContain "10:24: cannot assign different pointer type, expected ^^ubyte got ^^uword"
|
||||
errors.errors[0] shouldContain "7:24: cannot assign different pointer type, expected ^^uword or uword but got ^^ubyte"
|
||||
errors.errors[1] shouldContain "10:24: cannot assign different pointer type, expected ^^ubyte or uword but got ^^uword"
|
||||
}
|
||||
|
||||
test("passing nosplit array of structpointers to a subroutine in various forms should be param type ptr to struct") {
|
||||
@@ -2456,4 +2438,73 @@ structdefs {
|
||||
a5t.pointerDereference!!.chain shouldBe listOf("structdefs", "element", "value2")
|
||||
a6t.pointerDereference!!.chain shouldBe listOf("structdefs", "element", "value2")
|
||||
}
|
||||
|
||||
test("assigning field with same name should not confuse compiler") {
|
||||
val src="""
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
ubyte @shared ok = sprites[2].y ; this one is fine...
|
||||
ubyte @shared y = sprites[2].y ; this used to crash
|
||||
}
|
||||
|
||||
struct Sprite {
|
||||
uword x
|
||||
ubyte y
|
||||
}
|
||||
|
||||
^^Sprite[4] @shared sprites
|
||||
}"""
|
||||
compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("0-indexed optimizations") {
|
||||
val src="""
|
||||
main {
|
||||
struct Sprite {
|
||||
uword x
|
||||
ubyte y
|
||||
}
|
||||
|
||||
^^Sprite[4] @shared sprites
|
||||
^^Sprite @shared sprptr
|
||||
|
||||
sub start() {
|
||||
sprptr.y = 99
|
||||
sprptr[0]^^.y = 99
|
||||
;; sprites[0]^^.y = 99 ; no change here. TODO: this syntax doesn't compile yet...
|
||||
cx16.r0 = &sprptr[0]
|
||||
|
||||
cx16.r2L = sprptr.y
|
||||
cx16.r0L = sprptr[0].y
|
||||
cx16.r1L = sprites[0].y ; no change here, need first array element
|
||||
cx16.r0 = sprites[0] ; no change here, need first array element
|
||||
cx16.r0 = sprites[0] ; no change here, need first array element
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false)!!
|
||||
val st = result.compilerAst.entrypoint.statements
|
||||
st.size shouldBe 8
|
||||
val a1 = st[0] as Assignment
|
||||
val a2 = st[1] as Assignment
|
||||
val a3 = st[2] as Assignment
|
||||
val a4 = st[3] as Assignment
|
||||
val a5 = st[4] as Assignment
|
||||
val a6 = st[5] as Assignment
|
||||
val a7 = st[6] as Assignment
|
||||
|
||||
a1.target.arrayIndexedDereference shouldBe null
|
||||
a1.target.pointerDereference!!.chain shouldBe listOf("sprptr", "y")
|
||||
a2.target.arrayIndexedDereference shouldBe null
|
||||
a2.target.pointerDereference!!.chain shouldBe listOf("sprptr", "y")
|
||||
|
||||
(a3.value as? AddressOf)?.identifier?.nameInSource shouldBe listOf("sprptr")
|
||||
(a4.value as? PtrDereference)?.chain shouldBe listOf("sprptr", "y")
|
||||
(a5.value as? PtrDereference)?.chain shouldBe listOf("sprptr", "y")
|
||||
val be6 = a6.value as BinaryExpression // this one is an actual array and we need the first element so no change here
|
||||
be6.operator shouldBe "."
|
||||
be6.left shouldBe instanceOf<ArrayIndexedExpression>()
|
||||
be6.right shouldBe instanceOf<IdentifierReference>()
|
||||
(a7.value as? ArrayIndexedExpression)?.indexer?.constIndex() shouldBe 0
|
||||
}
|
||||
})
|
||||
@@ -240,6 +240,33 @@ main {
|
||||
errors.errors[1] shouldContain "9:25: invalid number of arguments"
|
||||
}
|
||||
|
||||
test("invalid number of args check on normal subroutine (in expression)") {
|
||||
val text="""
|
||||
main {
|
||||
sub thing(ubyte a1, ubyte a2) -> ubyte {
|
||||
return 42
|
||||
}
|
||||
|
||||
sub wrapper(ubyte value) {
|
||||
value++
|
||||
}
|
||||
|
||||
sub start() {
|
||||
wrapper(thing(1))
|
||||
wrapper(thing(1,2))
|
||||
wrapper(thing(1,2,3))
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "12:33: invalid number of arguments"
|
||||
errors.errors[1] shouldContain "14:33: invalid number of arguments"
|
||||
}
|
||||
|
||||
|
||||
test("invalid number of args check on asm subroutine") {
|
||||
val text="""
|
||||
main {
|
||||
@@ -261,6 +288,35 @@ main {
|
||||
errors.errors[1] shouldContain "9:25: invalid number of arguments"
|
||||
}
|
||||
|
||||
test("invalid number of args check on asm subroutine (in expression)") {
|
||||
val text="""
|
||||
main {
|
||||
asmsub thing(ubyte a1 @A, ubyte a2 @Y) -> ubyte @A {
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub wrapper(ubyte value) {
|
||||
value++
|
||||
}
|
||||
|
||||
sub start() {
|
||||
wrapper(thing(1))
|
||||
wrapper(thing(1,2))
|
||||
wrapper(thing(1,2,3))
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, text, outputDir, writeAssembly = false, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "14:33: invalid number of arguments"
|
||||
errors.errors[1] shouldContain "16:33: invalid number of arguments"
|
||||
}
|
||||
|
||||
|
||||
test("invalid number of args check on call to label and builtin func") {
|
||||
val text="""
|
||||
main {
|
||||
|
||||
@@ -88,8 +88,8 @@ class TestSymbolTable: FunSpec({
|
||||
val stVar1 = StStaticVariable("initialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0u, false,node)
|
||||
stVar1.setOnetimeInitNumeric(99.0)
|
||||
val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0u, false, node)
|
||||
val arrayInitNonzero = listOf(StArrayElement(1.1, null, null), StArrayElement(2.2, null, null), StArrayElement(3.3, null, null))
|
||||
val arrayInitAllzero = listOf(StArrayElement(0.0, null, null), StArrayElement(0.0, null, null), StArrayElement(0.0, null, null))
|
||||
val arrayInitNonzero = listOf(StArrayElement(1.1, null, null, null, null), StArrayElement(2.2, null, null, null, null), StArrayElement(3.3, null, null,null, null))
|
||||
val arrayInitAllzero = listOf(StArrayElement(0.0, null, null, null, null), StArrayElement(0.0, null, null,null, null), StArrayElement(0.0, null, null,null, null))
|
||||
val stVar3 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitNonzero, 3u, ZeropageWish.DONTCARE, 0u, false, node)
|
||||
val stVar4 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitAllzero, 3u, ZeropageWish.DONTCARE, 0u, false, node)
|
||||
val stVar5 = StStaticVariable("uninitialized", DataType.arrayFor(BaseDataType.UWORD), null, null, 3u, ZeropageWish.DONTCARE, 0u, false, node)
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.AstToSourceTextConverter
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.PROG8_CONTAINER_MODULES
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
@@ -38,10 +38,12 @@ class TestAstToSourceText: AnnotationSpec() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMentionsInternedStringsModule() {
|
||||
fun testMentionsProg8ContainerModules() {
|
||||
val orig = SourceCode.Text("\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex(";.*$INTERNED_STRINGS_MODULENAME")
|
||||
PROG8_CONTAINER_MODULES.forEach {
|
||||
txt shouldContain Regex(";.*$it")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -13,10 +13,11 @@ import io.kotest.matchers.types.shouldBeSameInstanceAs
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.PROG8_CONTAINER_MODULES
|
||||
import prog8.code.ast.PtBlock
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.target.C64Target
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
@@ -30,7 +31,7 @@ class TestProgram: FunSpec({
|
||||
context("Constructor") {
|
||||
test("withNameBuiltinsAndMemsizer") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.modules.size shouldBe 1
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size
|
||||
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
|
||||
program.modules[0].program shouldBeSameInstanceAs program
|
||||
program.modules[0].parent shouldBeSameInstanceAs program.namespace
|
||||
@@ -45,7 +46,7 @@ class TestProgram: FunSpec({
|
||||
val retVal = program.addModule(m1)
|
||||
|
||||
retVal shouldBeSameInstanceAs program
|
||||
program.modules.size shouldBe 2
|
||||
program.modules.size shouldBe PROG8_CONTAINER_MODULES.size + 1
|
||||
m1 shouldBeIn program.modules
|
||||
m1.program shouldBeSameInstanceAs program
|
||||
m1.parent shouldBeSameInstanceAs program.namespace
|
||||
@@ -163,7 +164,7 @@ datablock2 ${'$'}8000 {
|
||||
|
||||
val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true)!!
|
||||
result.compilerAst.allBlocks.size shouldBeGreaterThan 5
|
||||
result.compilerAst.modules.drop(2).all { it.isLibrary } shouldBe true
|
||||
result.compilerAst.modules.drop(PROG8_CONTAINER_MODULES.size+1).all { it.isLibrary } shouldBe true
|
||||
val mainMod = result.compilerAst.modules[0]
|
||||
mainMod.name shouldStartWith "on_the_fly"
|
||||
result.compilerAst.modules[1].name shouldBe "prog8_interned_strings"
|
||||
@@ -183,7 +184,7 @@ datablock2 ${'$'}8000 {
|
||||
blocks[1].name shouldBe "p8_sys_startup"
|
||||
blocks[2].name shouldBe "p8b_otherblock1"
|
||||
blocks[3].name shouldBe "p8b_otherblock2"
|
||||
blocks[4].name shouldBe "prog8_interned_strings"
|
||||
blocks[4].name shouldBe INTERNED_STRINGS_MODULENAME
|
||||
blocks[5].name shouldBe "txt"
|
||||
blocks[5].library shouldBe true
|
||||
blocks[13].name shouldBe "p8b_datablock2"
|
||||
|
||||
@@ -1157,5 +1157,46 @@ main {
|
||||
decls.all { it.datatype.isPointerArray } shouldBe true
|
||||
}
|
||||
|
||||
test("on..call in nested scope compiles correctly with temp variable introduced") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
if_cs {
|
||||
on cx16.r0L call (func,func)
|
||||
}
|
||||
}
|
||||
|
||||
sub func() {
|
||||
}
|
||||
}"""
|
||||
|
||||
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("breakpoint after expression") {
|
||||
val src="""
|
||||
main {
|
||||
ubyte a,b
|
||||
|
||||
sub start() {
|
||||
func1()
|
||||
func2()
|
||||
}
|
||||
|
||||
sub func1() {
|
||||
a = b
|
||||
%breakpoint ; parse error
|
||||
}
|
||||
|
||||
sub func2() {
|
||||
a = b
|
||||
%breakpoint! ; parse ok
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), optimize=false, src, outputDir, errors=errors) shouldBe null
|
||||
errors.errors.size shouldBe 3
|
||||
errors.errors[1] shouldContain "12:10: undefined symbol: breakpoint"
|
||||
}
|
||||
})
|
||||
|
||||
@@ -213,7 +213,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
output(") ")
|
||||
if(subroutine.asmClobbers.isNotEmpty()) {
|
||||
output("-> clobbers (")
|
||||
output(" clobbers (")
|
||||
val regs = subroutine.asmClobbers.toList().sorted()
|
||||
for(r in regs) {
|
||||
output(r.toString())
|
||||
@@ -545,9 +545,9 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(onGoto: OnGoto) {
|
||||
output(if(onGoto.isCall) "selectcall " else "selectgoto ")
|
||||
output("on ")
|
||||
onGoto.index.accept(this)
|
||||
output(" from (")
|
||||
output(if(onGoto.isCall) " call (" else " goto (")
|
||||
onGoto.labels.forEachIndexed { idx, label ->
|
||||
label.accept(this)
|
||||
if(idx!=onGoto.labels.lastIndex)
|
||||
@@ -571,7 +571,18 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(deref: ArrayIndexedPtrDereference) {
|
||||
output("???? Array Indexed Ptr Dereference should have been converted to other AST nodes ????")
|
||||
for(c in deref.chain) {
|
||||
output(c.first)
|
||||
c.second?.let {
|
||||
output("[")
|
||||
it.accept(this)
|
||||
output("]")
|
||||
}
|
||||
if(c !== deref.chain.last())
|
||||
output("^^.")
|
||||
}
|
||||
if(deref.derefLast)
|
||||
output("^^")
|
||||
}
|
||||
|
||||
override fun visit(initializer: StaticStructInitializer) {
|
||||
|
||||
@@ -427,6 +427,7 @@ fun defaultZero(dt: BaseDataType, position: Position) = when(dt) {
|
||||
BaseDataType.BYTE -> NumericLiteral(BaseDataType.BYTE, 0.0, position)
|
||||
BaseDataType.UWORD, BaseDataType.STR -> NumericLiteral(BaseDataType.UWORD, 0.0, position)
|
||||
BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, 0.0, position)
|
||||
BaseDataType.LONG -> NumericLiteral(BaseDataType.LONG, 0.0, position)
|
||||
BaseDataType.FLOAT -> NumericLiteral(BaseDataType.FLOAT, 0.0, position)
|
||||
BaseDataType.POINTER -> NumericLiteral(BaseDataType.UWORD, 0.0, position)
|
||||
else -> throw FatalAstException("can only determine default zero value for a numeric type")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.code.core.Position
|
||||
|
||||
/**
|
||||
@@ -19,6 +18,3 @@ open class SyntaxError(override var message: String, val position: Position) : A
|
||||
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
||||
override fun toString() = "${position.toClickableStr()} Error: $message"
|
||||
}
|
||||
|
||||
class UndefinedSymbolError(symbol: IdentifierReference)
|
||||
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||
@@ -6,6 +6,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.GENERATED_LABEL_PREFIX
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.PROG8_CONTAINER_MODULES
|
||||
import prog8.code.core.*
|
||||
import prog8.code.source.SourceCode
|
||||
|
||||
@@ -22,17 +23,19 @@ class Program(val name: String,
|
||||
val namespace: GlobalNamespace = GlobalNamespace(_modules)
|
||||
|
||||
init {
|
||||
// insert a container module for all interned strings later
|
||||
val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(INTERNED_STRINGS_MODULENAME))
|
||||
val block = Block(INTERNED_STRINGS_MODULENAME, null, mutableListOf(), true, Position.DUMMY)
|
||||
val directive = Directive("%option", listOf(DirectiveArg("no_symbol_prefixing", null, Position.DUMMY)), Position.DUMMY)
|
||||
block.statements.add(directive)
|
||||
directive.linkParents(block)
|
||||
internedStringsModule.statements.add(block)
|
||||
// insert container modules for all interned strings and struct instances
|
||||
PROG8_CONTAINER_MODULES.forEach { containername ->
|
||||
val module = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(containername))
|
||||
val block = Block(containername, null, mutableListOf(), true, Position.DUMMY)
|
||||
val directive = Directive("%option", listOf(DirectiveArg("no_symbol_prefixing", null, Position.DUMMY)), Position.DUMMY)
|
||||
block.statements.add(directive)
|
||||
directive.linkParents(block)
|
||||
module.statements.add(block)
|
||||
|
||||
_modules.add(0, internedStringsModule)
|
||||
internedStringsModule.linkParents(namespace)
|
||||
internedStringsModule.program = this
|
||||
_modules.add(0, module)
|
||||
module.linkParents(namespace)
|
||||
module.program = this
|
||||
}
|
||||
}
|
||||
|
||||
fun addModule(module: Module): Program {
|
||||
@@ -67,7 +70,7 @@ class Program(val name: String,
|
||||
}
|
||||
|
||||
val toplevelModule: Module
|
||||
get() = modules.first { it.name!= INTERNED_STRINGS_MODULENAME }
|
||||
get() = modules.first { it.name !in PROG8_CONTAINER_MODULES }
|
||||
|
||||
private val internedStringsReferenceCounts = mutableMapOf<VarDecl, Int>()
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ private class SymbolDumper(val skipLibraries: Boolean): IAstVisitor {
|
||||
}
|
||||
output(") ")
|
||||
if(subroutine.asmClobbers.isNotEmpty()) {
|
||||
output("-> clobbers (")
|
||||
output(" clobbers (")
|
||||
val regs = subroutine.asmClobbers.toList().sorted()
|
||||
for(r in regs) {
|
||||
output(r.toString())
|
||||
|
||||
@@ -179,7 +179,12 @@ class BinaryExpression(
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean {
|
||||
return if(operator==".")
|
||||
left.referencesIdentifier(nameInSource)
|
||||
else
|
||||
left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
|
||||
}
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
|
||||
val leftDt = left.inferType(program)
|
||||
@@ -1123,7 +1128,7 @@ class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because
|
||||
}
|
||||
|
||||
// otherwise, select the "biggest" datatype based on the elements in the array.
|
||||
require(value.isNotEmpty()) { "can't determine type of empty array" }
|
||||
if(value.isEmpty()) return InferredTypes.unknown()
|
||||
val datatypesInArray = value.map { it.inferType(program) }
|
||||
if(datatypesInArray.any{ it.isUnknown })
|
||||
return InferredTypes.unknown()
|
||||
@@ -1318,9 +1323,30 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
BuiltinFunctionPlaceholder(nameInSource[0], position, parent)
|
||||
else
|
||||
definingScope.lookup(nameInSource)
|
||||
fun targetVarDecl(): VarDecl? = targetStatement() as? VarDecl
|
||||
fun targetSubroutine(): Subroutine? = targetStatement() as? Subroutine
|
||||
fun targetStructDecl(): StructDecl? = targetStatement() as? StructDecl
|
||||
fun targetVarDecl(): VarDecl? {
|
||||
// follows aliases
|
||||
val t = targetStatement()
|
||||
return if(t is Alias)
|
||||
t.target.targetVarDecl()
|
||||
else
|
||||
t as? VarDecl
|
||||
}
|
||||
fun targetSubroutine(): Subroutine? {
|
||||
// follows aliases
|
||||
val t = targetStatement()
|
||||
return if(t is Alias)
|
||||
t.target.targetSubroutine()
|
||||
else
|
||||
t as? Subroutine
|
||||
}
|
||||
fun targetStructDecl(): StructDecl? {
|
||||
// follows aliases
|
||||
val t = targetStatement()
|
||||
return if(t is Alias)
|
||||
t.target.targetStructDecl()
|
||||
else
|
||||
t as? StructDecl
|
||||
}
|
||||
fun targetStructFieldRef(): StructFieldRef? {
|
||||
if(nameInSource.size<2) return null
|
||||
return targetStatement() as? StructFieldRef
|
||||
@@ -1783,6 +1809,35 @@ class ArrayIndexedPtrDereference(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if(parent !is BinaryExpression || (parent as BinaryExpression).operator!=".") {
|
||||
val indexPosition = this.chain.indexOfFirst { it.second!=null }
|
||||
val identifier = this.chain.take(indexPosition+1).map {it.first}
|
||||
val target = this.definingScope.lookup(identifier)
|
||||
if(target is VarDecl && (target.datatype.isPointer || target.datatype.isPointerArray)) {
|
||||
val sub = target.datatype.subType
|
||||
if(sub!=null) {
|
||||
val field = this.chain.drop(indexPosition+1).singleOrNull()
|
||||
if(field==null) {
|
||||
return InferredTypes.knownFor(DataType.structInstance(sub))
|
||||
} else {
|
||||
val fieldDt= sub.getFieldType(field.first)!!
|
||||
return if(derefLast)
|
||||
InferredTypes.knownFor(fieldDt.dereference())
|
||||
else
|
||||
InferredTypes.knownFor(fieldDt)
|
||||
}
|
||||
} else {
|
||||
require(indexPosition==this.chain.size-1) {"when indexing primitive pointer type there cannot be a field after it"}
|
||||
return if(derefLast)
|
||||
InferredTypes.knownFor(target.datatype.dereference())
|
||||
else
|
||||
InferredTypes.knownFor(target.datatype)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// too hard to determine the type....?
|
||||
return InferredTypes.unknown()
|
||||
}
|
||||
@@ -1820,7 +1875,13 @@ class StaticStructInitializer(var structname: IdentifierReference,
|
||||
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = structname.referencesIdentifier(nameInSource) || args.any{it.referencesIdentifier(nameInSource)}
|
||||
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(BaseDataType.UWORD)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val struct = structname.targetStructDecl()
|
||||
return if(struct==null)
|
||||
InferredTypes.unknown()
|
||||
else
|
||||
InferredTypes.knownFor(DataType.pointer(struct))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ class VarDecl(
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && (value==null || node===value))
|
||||
value = replacement // note: any datatype differences between the value and the decl itself, will be fixed by a separate ast walker step
|
||||
replacement.parent = this
|
||||
replacement.linkParents(this)
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
@@ -414,6 +414,15 @@ class StructDecl(override val name: String, val fields: Array<Pair<DataType, Str
|
||||
|
||||
override fun getFieldType(name: String): DataType? = fields.firstOrNull { it.second==name }?.first
|
||||
override val scopedNameString by lazy { scopedName.joinToString(".") }
|
||||
|
||||
fun offsetof(fieldname: String, sizer: IMemSizer): UByte? {
|
||||
fields.fold(0) { offset, field ->
|
||||
if (field.second == fieldname)
|
||||
return offset.toUByte()
|
||||
offset + sizer.memorySize(field.first, 1)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class StructFieldRef(val pointer: IdentifierReference, val struct: StructDecl, val type: DataType, override val name: String, override val position: Position): Statement(), INamedStatement {
|
||||
|
||||
@@ -80,9 +80,9 @@ Subroutines
|
||||
Pointers and Structs
|
||||
--------------------
|
||||
|
||||
Legacy 'untyped' pointers:
|
||||
*Legacy 'untyped' pointers:*
|
||||
|
||||
- In Prog8 versions before 12.0 there was no support for typed pointers, only 'untyped' ones:
|
||||
- In Prog8 versions **before 12.0** there was no support for typed pointers, only 'untyped' ones:
|
||||
Variables of the ``uword`` datatype can be used as a pointer to one of the possible 65536 memory locations,
|
||||
so the value it points to is always a single byte. This is similar to ``uint8_t*`` from C.
|
||||
You have to deal with the uword manually if the object it points to is something different.
|
||||
@@ -92,9 +92,9 @@ Legacy 'untyped' pointers:
|
||||
- Pointers don't have to be a variable, you can immediately access the value of a given memory location using ``@($d020)`` for instance.
|
||||
Reading is done by assigning it to a variable, writing is done by just assigning the new value to it.
|
||||
|
||||
Typed pointers and structs:
|
||||
*Typed pointers and structs:*
|
||||
|
||||
- Since version 12, prog8 supports struct types and typed pointers.
|
||||
- Since **version 12.0**, prog8 supports struct types and typed pointers.
|
||||
- Structs are a grouping of one or more fields, that together make up the struct type.
|
||||
- Typed pointers are just that: a pointer to a specific type (which can be a simple type such as float, or a struct type.)
|
||||
|
||||
|
||||
+4
-4
@@ -52,10 +52,7 @@ needs_sphinx = '5.3'
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [ 'sphinxcontrib.jquery', 'sphinx_rtd_dark_mode']
|
||||
|
||||
# user starts in light mode
|
||||
default_dark_mode = False
|
||||
extensions = [ 'sphinxcontrib.jquery', 'sphinx_rtd_dark_mode', 'sphinx.ext.imgconverter']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -177,6 +174,9 @@ texinfo_documents = [
|
||||
|
||||
# -- Extension configuration -------------------------------------------------
|
||||
|
||||
# user starts in light mode
|
||||
default_dark_mode = False
|
||||
|
||||
# -- Options for to do extension ----------------------------------------------
|
||||
|
||||
# todo_include_todos = True
|
||||
|
||||
@@ -118,6 +118,11 @@ mkword (msb, lsb)
|
||||
Don't get confused by how the system actually stores this 16-bit word value in memory (which is
|
||||
in little-endian format, so lsb first then msb)
|
||||
|
||||
offsetof (Struct.field)
|
||||
The offset in bytes of the given field in the struct. The first field will always have offset 0.
|
||||
Usually you just reference the fields directly but in some cases it might be useful to know how many
|
||||
bytes from the start of the structure a field is located at.
|
||||
|
||||
peek (address)
|
||||
same as @(address) - reads the byte at the given address in memory.
|
||||
|
||||
@@ -668,7 +673,7 @@ but perhaps the provided ones can be of service too.
|
||||
Returns next random byte 0-255 from the pseudo-RNG sequence.
|
||||
Does not work in ROM code; use rnd_rom instead.
|
||||
|
||||
``rnd ()``
|
||||
``rnd_rom ()``
|
||||
Returns next random byte 0-255 from the pseudo-RNG sequence.
|
||||
Works in ROM code, but make sure to initialize the seed values using rndseed_rom.
|
||||
|
||||
|
||||
@@ -5,9 +5,15 @@
|
||||
Porting Guide
|
||||
*************
|
||||
|
||||
Here is a guide for porting Prog8 to other compilation targets.
|
||||
Here is a guide for making Prog8 work for other compilation targets.
|
||||
Answers to the questions below are used to configure the new target and supporting libraries.
|
||||
|
||||
**It is not required to change the compiler itself to make it support new compilation targets.**
|
||||
A few of the most commonly used ones are built-in (such as c64, cx16), but you can use
|
||||
separate configuration files to create new targets that the compiler can use.
|
||||
See :ref:`customizable_target` for details about this. You still need to provide most of the
|
||||
information asked for in this porting guide and code that into the configuration file.
|
||||
|
||||
.. note::
|
||||
The assembly code that prog8 generates is not suitable to be put into ROM. (It contains
|
||||
embedded variables, and self-modifying code).
|
||||
@@ -25,7 +31,7 @@ Memory Map
|
||||
|
||||
Zeropage
|
||||
========
|
||||
#. *Absolute requirement:* Provide four times 2 consecutive bytes (i.e. four 16-bit words) in the zeropage that are free to use at all times.
|
||||
#. *Absolute requirement:* Provide four words (16 bit byte pairs) in the zeropage that are free to use at all times.
|
||||
#. Provide list of any additional free zeropage locations for a normal running system (BASIC + Kernal enabled)
|
||||
#. Provide list of any additional free zeropage locations when BASIC is off, but floating point routines should still work
|
||||
#. Provide list of any additional free zeropage locations when only the Kernal remains enabled
|
||||
@@ -43,14 +49,13 @@ RAM, ROM, I/O
|
||||
#. what part(s) of the address space is memory-mapped I/O registers?
|
||||
#. is there a block of "high ram" available (ram that is not the main ram used to load programs in) that could be used for variables?
|
||||
#. is there a banking system? How does it work (how do you select Ram/Rom banks)? How is the default bank configuration set?
|
||||
Note that prog8 itself has no notion of banking, but this knowledge may be required for proper system initialization.
|
||||
|
||||
Character encodings
|
||||
-------------------
|
||||
#. if not PETSCII or CBM screencodes: provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
|
||||
#. provide the primary character encoding table that the system uses (i.e. how is text represented in memory. For example, PETSCII)
|
||||
#. provide alternate character encodings (if any)
|
||||
#. what are the system's standard character screen dimensions?
|
||||
#. is there a screen character matrix directly accessible in Ram? What's it address? Same for color attributes if any.
|
||||
#. is there a screen character matrix directly accessible in RAM? What's it address? Same for color attributes if any.
|
||||
|
||||
|
||||
ROM routines
|
||||
@@ -63,7 +68,7 @@ The more the merrier.
|
||||
|
||||
Floating point
|
||||
==============
|
||||
Prog8 can support floating point math *if* the target system has floating point math routines in ROM. If that is the case:
|
||||
Prog8 can support floating point math *if* the target system has suitable floating point math routines in ROM. If that is the case:
|
||||
|
||||
#. what is the binary representation format of the floating point numbers? (how many bytes, how the bits are set up)
|
||||
#. what are the valid minimum negative and maximum positive floating point values?
|
||||
|
||||
@@ -340,10 +340,20 @@ Directives
|
||||
}
|
||||
|
||||
|
||||
.. data:: %breakpoint
|
||||
.. data:: %breakpoint! or %breakpoint
|
||||
|
||||
Level: not at module scope.
|
||||
Defines a debugging breakpoint at this location. See :ref:`debugging`
|
||||
The version with the explamation point '!' at the end can be used even
|
||||
if the breakpoint follows an expression. If you don't use the '!' version in this case
|
||||
the compiler may think it is just a term in the expression (modulo operator and breakpoint operand value),
|
||||
instead of a breakpoint directive::
|
||||
|
||||
a = b
|
||||
%breakpoint ; parse error because it thinks it is part of the previous line
|
||||
|
||||
a = b
|
||||
%breakpoint! ; parsed correctly as directive
|
||||
|
||||
|
||||
.. data:: %encoding <encodingname>
|
||||
@@ -1303,6 +1313,11 @@ It's possible to write a defer for a block of statements, but the advice is to k
|
||||
If a piece of inlined assembly somehow causes the routine to exit, the compiler cannot detect this,
|
||||
and defers won't be handled in such cases.
|
||||
|
||||
.. attention::
|
||||
Using defer always has a slight code overhead.
|
||||
If you are returning non-constant values in a routine that uses defer, the compiler even has to insert some additional
|
||||
code that uses the cpu stack to save some temporary values.
|
||||
|
||||
|
||||
Library routines and builtin functions
|
||||
--------------------------------------
|
||||
|
||||
@@ -15,21 +15,24 @@ Structs and Pointers
|
||||
priority over other variables to be placed into zeropage.
|
||||
|
||||
.. note::
|
||||
Due to some limitations in the language parser, not all pointer related syntax is currently supported
|
||||
if it is a pointer to a struct type.
|
||||
Due to a few limitations in the language parser, some pointer related syntax is currently unsupported.
|
||||
The compiler tries its best to give a descriptive error message but sometimes there is still a
|
||||
parser limitation that has to be worked around at the moment. For example, this syntax is not supported
|
||||
right now and will result in a parse error::
|
||||
parser limitation that has to be worked around at the moment. For example, this assigment syntax doesn't parse correctly::
|
||||
|
||||
^^Node np
|
||||
np[2].field = 9999
|
||||
np[2].field = 9999 ; cannot use this syntax as assignment target right now
|
||||
ubyte value = np[2].field ; note that using it as expression value works fine
|
||||
|
||||
To work around this (and similar) cases you'll have to break up the expression in multiple steps,
|
||||
in this case something like::
|
||||
To work around this you'll have to explicitly write the pointer dereferencing operator,
|
||||
or break up the expression in multiple steps (which can be beneficial too when you are assigning multiple fields
|
||||
because it will save a pointer calculation for every assignment)::
|
||||
|
||||
^^Node thirdnode = &&np[2]
|
||||
thirdnode.field = 9999
|
||||
^^Node np
|
||||
np[2]^^.field = 9999
|
||||
|
||||
; alternatively, split up:
|
||||
^^Node thenode = &&np[2]
|
||||
thenode.field = 9999
|
||||
|
||||
|
||||
Legacy untyped pointers (uword)
|
||||
@@ -130,7 +133,7 @@ dealing with all of them separately. You first define the struct type like so::
|
||||
You can use boolean fields, numeric fields (byte, word, float), and pointer fields (including str, which is translated into ^^ubyte).
|
||||
You cannot nest struct types nor put arrays in them as a field.
|
||||
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 size of a struct.
|
||||
``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).
|
||||
|
||||
You can copy the whole contents of a struct to another one by assigning the dereferenced pointers::
|
||||
@@ -139,21 +142,24 @@ You can copy the whole contents of a struct to another one by assigning the dere
|
||||
e1^^ = e2^^ ; copies all fields of e2 into e1
|
||||
|
||||
|
||||
The struct type creates a new name scape, so accessing the fields of a struct is done as usual with the dotted notation::
|
||||
The struct type creates a new name scape, so accessing the fields of a struct is done as usual with the dotted notation.
|
||||
Because it implies pointer dereferencing you can usually omit the explicit `^^`, prog8 will know what it means::
|
||||
|
||||
if e1.ypos > 300
|
||||
e1.health -= 10
|
||||
|
||||
; notice that that implicitly dereferences the pointer variable, actually it is doing this:
|
||||
; explicit dereferencing notation:
|
||||
|
||||
if e1^^.ypos > 300
|
||||
e1^^.health -= 10
|
||||
|
||||
|
||||
.. note::
|
||||
Structs are only supported as a *reference type* (via a pointer). It is currently not possible to use them as a value type.
|
||||
Structs are currently only supported as a *reference type* (they always have to be accessed through a pointer).
|
||||
It is not yet possible to use them as a value type, or as memory-mapped types.
|
||||
This means you cannot create an array of structs either - only arrays of pointers to structs.
|
||||
There are a couple of simple case where the compiler allows assignment of struct instances. You are allowed to write::
|
||||
There are a couple of simple case where the compiler does allow assignment of struct instances though, and it will
|
||||
automatically copy all the fields for you. You are allowed to write::
|
||||
|
||||
ptr2^^ = ptr1^^
|
||||
ptr2^^ = ptr1[2]
|
||||
@@ -163,14 +169,14 @@ The struct type creates a new name scape, so accessing the fields of a struct is
|
||||
In the future more cases may be supported.
|
||||
|
||||
.. note::
|
||||
Using structs instead of plain arrays may result in less efficent code being generated.
|
||||
Using structs instead of plain arrays usually results in more and less efficent code being generated.
|
||||
This is because the 6502 CPU is not particularly well equipped to dealing with pointers and accessing struct fields via offsets,
|
||||
as compared to direct variable access or array indexing. The prog8 program code may be easier to work with though!
|
||||
|
||||
.. note::
|
||||
Accessing the first field in a struct is more efficient than subsequent fields, because it
|
||||
is at offset 0 so no additional addition has to be done on a pointer to reach the first field.
|
||||
Try to put the most often accessed field as the first field to gain a rather substantial code size and efficiency boost.
|
||||
is at offset 0 so no additional addition has to be computed on a pointer to reach the first field.
|
||||
Try to put the most often accessed field as the first field to potentially gain a rather substantial boost in code efficiency.
|
||||
|
||||
|
||||
Static initialization of structs
|
||||
@@ -184,13 +190,40 @@ called multiple times, or inside a loop, the struct *will be the same instance e
|
||||
Read below if you need *dynamic* struct allocation!
|
||||
You write a static struct initialization expression like this:
|
||||
|
||||
``^^Node : [1,2,3,4]``
|
||||
statically places an instance of struct 'Node' in memory, with its fields set to 1,2,3,4 and returns the address of this struct.
|
||||
``^^Node : [1,"one", 1000, true, 1.111]``
|
||||
statically places an instance of struct 'Node' in memory, with its fields set to 1, "one", 1000 etcetera and returns the address of this struct.
|
||||
The values in the initialization array must correspond exactly with the first to last declared fields in the struct type.
|
||||
``^^Node : []``
|
||||
(without values) Places a 'Node' instance in BSS variable space instead, which gets zeroed out at program startup.
|
||||
Returns the address of this empty struct.
|
||||
|
||||
It is also possible to put struct initializer inside arrays to make them all statically initialized and accessible via the array::
|
||||
|
||||
^^Node[] allnodes = [
|
||||
^^Node: [1,"one", 1000, true, 1.111],
|
||||
^^Node: [2,"two", 2000, false, 2.222],
|
||||
^^Node: [],
|
||||
^^Node: [],
|
||||
]
|
||||
|
||||
Short form initializers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If the required type can be inferred from the context you can also omit the struct pointer type prefix altogether.
|
||||
The initializer value then is syntactically the same as an array, but Prog8 internally turns it back into a proper
|
||||
struct initializer value based on the the type of the array element or pointer variable it is assigned to.
|
||||
So you can write the above in short form as::
|
||||
|
||||
^^Node nodepointer = [1,2,3,4]
|
||||
|
||||
^^Node[] allnodes = [
|
||||
[1,"one", 1000, true, 1.111],
|
||||
[2,"two", 2000, false, 2.222],
|
||||
[],
|
||||
[]
|
||||
]
|
||||
|
||||
|
||||
|
||||
Dynamic allocation of structs
|
||||
=============================
|
||||
|
||||
@@ -16,6 +16,7 @@ Currently these machines can be selected as a compilation target (via the ``-tar
|
||||
- 'c128': the Commodore 128 (*limited support*)
|
||||
- 'pet32': the Commodore PET 4032 (*limited support*)
|
||||
- 'virtual': a builtin virtual machine
|
||||
- custom targets via a separate configuration file (see :ref:`customizable_target`)
|
||||
|
||||
This chapter explains some relevant system details of the c64 and cx16 machines.
|
||||
|
||||
@@ -195,6 +196,8 @@ These routines are::
|
||||
|
||||
sys.set_irq(uword handler_address)
|
||||
sys.set_rasterirq(uword handler_address, uword rasterline)
|
||||
sys.update_rasterirq(uword handler_address, uword rasterline)
|
||||
sys.set_rasterline(uword rasterline)
|
||||
sys.restore_irq() ; set everything back to the systems default irq handler
|
||||
|
||||
The IRQ handler routine must return a boolean value (0 or 1) in the A register:
|
||||
|
||||
+98
-82
@@ -1,6 +1,101 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
|
||||
STRUCTS and TYPED POINTERS
|
||||
--------------------------
|
||||
|
||||
- implement the remaining TODO's in PointerAssignmentsGen.
|
||||
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
|
||||
- optimize the float copying in assignIndexedPointer() (also word?)
|
||||
- optimize augmented assignments to indexed pointer targets like sprptr[2]^^.y++ (these are now not performend in-place but as a regular assignment)
|
||||
- implement even more struct instance assignments (via memcopy) in CodeDesugarer (see the TODO) (add to documentation as well, paragraph 'Structs')
|
||||
- support @nosplit pointer arrays?
|
||||
- support pointer to pointer?
|
||||
- support for typed function pointers? (&routine could be typed by default as well then)
|
||||
- really fixing the pointer dereferencing issues (cursed hybrid beween IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator"
|
||||
- (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)
|
||||
|
||||
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- improve ANTLR grammar with better error handling (as suggested by Qwen AI)
|
||||
- allow memory() to occur in array initializer
|
||||
- when a complete block is removed because unused, suppress all info messages about everything in the block being removed
|
||||
- fix the line, cols in Position, sometimes they count from 0 sometimes from 1
|
||||
- is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
|
||||
- enums?
|
||||
- romable: should we have a way to explicitly set the memory address for the BSS area (add a -varsaddress and -slabsaddress options?)
|
||||
- romable: fix remaining codegens (some for loops, see ForLoopsAsmGen)
|
||||
- Kotlin: can we use inline value classes in certain spots? (domain types instead of primitives)
|
||||
- add float support to the configurable compiler targets
|
||||
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
|
||||
- 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
|
||||
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
|
||||
- Improve register load order in subroutine call args assignments:
|
||||
in certain situations (need examples!), the "wrong" order of evaluation of function call arguments is done which results
|
||||
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)
|
||||
Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters().
|
||||
- Does it make codegen easier if everything is an expression? Start with the PtProgram ast classes, change statements to expressions that have (new) VOID data type
|
||||
- Can we support signed % (remainder) somehow?
|
||||
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful once we have typed pointers. (addressed in 'struct' branch)
|
||||
- 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)
|
||||
- [much work:] more support for (64tass) SEGMENTS in the prog8 syntax itself?
|
||||
- ability to use a sub instead of only a var for @bank ? what for though? dynamic bank/overlay loading?
|
||||
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions
|
||||
|
||||
|
||||
IR/VM
|
||||
-----
|
||||
- make MSIG instruction set flags and skip cmp #0 afterwards (if msb(x)>0)
|
||||
- is it possible to use LOADFIELD/STOREFIELD instructions even more?
|
||||
- make multiple classes of registers and maybe also categorize by life time , to prepare for better register allocation in the future
|
||||
SYSCALL_ARGS, // Reserved for syscall arguments (r99000-99099, r99100-99199)
|
||||
FUNCTION_PARAMS, // For passing function parameters
|
||||
FUNCTION_RETURNS, // For function return values
|
||||
TEMPORARY, // Short-lived temporary values
|
||||
LOCAL_VARIABLES, // Local variables within functions
|
||||
GLOBAL_VARIABLES, // Global/static variables
|
||||
HARDWARE_MAPPED, // Mapped to CPU hardware registers
|
||||
LOOP_INDICES, // Used as loop counters
|
||||
ADDRESS_CALCULATION // Used for pointer arithmetic
|
||||
Categorizing registers by lifetime can significantly improve allocation:
|
||||
- Short-lived: Temporary registers used in expressions
|
||||
- Medium-lived: Local variables within a function
|
||||
Registers could be categorized by how frequently they're accessed:
|
||||
- Hot Registers: Frequently accessed (should be allocated to faster physical registers)
|
||||
- Warm Registers: Moderately accessed
|
||||
- Cold Registers: Rarely accessed (can be spilled to memory if needed)
|
||||
We already have type-based pools
|
||||
- byte, word, float registers
|
||||
|
||||
- pointer dt's are all reduced to just an uword (in the irTypeString method) - is this okay or could it be beneficial to reintroduce the actual pointer type information? See commit 88b074c208450c58aa32469745afa03e4c5f564a
|
||||
- change the instruction format so an indirect register (a pointer) can be used more often, at least for the inplace assignment operators that operate on pointer
|
||||
- getting it in shape for code generation...: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!)
|
||||
- fix call() return value handling (... what's wrong with it again?)
|
||||
- encode asmsub/extsub clobber info in the call , or maybe include these definitions in the p8ir file itself too. (return registers are already encoded in the CALL instruction)
|
||||
- proper code gen for the CALLI instruction and that it (optionally) returns a word value that needs to be assigned to a reg
|
||||
- implement fast code paths for TODO("inplace split....
|
||||
- implement more TODOs in AssignmentGen
|
||||
- do something with the 'split' tag on split word arrays
|
||||
- add more optimizations in IRPeepholeOptimizer
|
||||
- apparently for SSA form, the IRCodeChunk is not a proper "basic block" yet because the last operation should be a branch or return, and no other branches
|
||||
- reduce register usage via linear-scan algorithm (based on live intervals) https://anoopsarkar.github.io/compilers-class/assets/lectures/opt3-regalloc-linearscan.pdf
|
||||
don't forget to take into account the data type of the register when it's going to be reused!
|
||||
- idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
|
||||
global initialization values are simply a list of LOAD instructions.
|
||||
Variables replaced include all subroutine parameters! So the only variables that remain as variables are arrays and strings.
|
||||
- the @split arrays are currently also split in _lsb/_msb arrays in the IR, and operations take multiple (byte) instructions that may lead to verbose and slow operation and machine code generation down the line.
|
||||
maybe another representation is needed once actual codegeneration is done from the IR...?
|
||||
- ExpressionCodeResult: get rid of the separation between single result register and multiple result registers? maybe not, this requires hundreds of lines to change
|
||||
- sometimes source lines end up missing in the output p8ir, for example the first assignment is gone in:
|
||||
sub start() {
|
||||
cx16.r0L = cx16.r1 as ubyte
|
||||
cx16.r0sL = cx16.r1s as byte }
|
||||
more detailed example:
|
||||
|
||||
not all source lines are correctly reported in the IR file,
|
||||
for example the below subroutine only shows the sub() line::
|
||||
|
||||
@@ -55,89 +150,9 @@ and for example the below code omits line 5::
|
||||
}
|
||||
|
||||
|
||||
STRUCTS and TYPED POINTERS
|
||||
--------------------------
|
||||
|
||||
- allow struct initialization syntax in an array such as [ Node(), Node(), Node() ], update sorting example to use list of countries like that
|
||||
- fix code size regressions (if any left)
|
||||
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
|
||||
- update structpointers.rst docs with 6502 specific things?
|
||||
- implement the remaining TODO's in PointerAssignmentsGen.
|
||||
- optimize the float copying in assignIndexedPointer() (also word?)
|
||||
- implement even more struct instance assignments (via memcopy) in CodeDesugarer (see the TODO) (add to documentation as well, paragraph 'Structs')
|
||||
- try to optimize pointer arithmetic used in peek/poke a bit more so the routines in sorting module can use typed pointers without increasing code size, see test.p8 in commit d394dc1e
|
||||
- should @(wordpointer) be equivalent to wordpointer^^ (that would require a LOT of code rewrite that now knows that @() is strictly byte based) ?
|
||||
or do an implicit cast @(wpointer as ubyte^^) ? And/or add a warning about that?
|
||||
- add struct and pointer benchmark to benchmark program?
|
||||
- optimize addUnsignedByteOrWordToAY in PointerAssignmentsGen a bit more
|
||||
- support for typed function pointers? (&routine could be typed by default as well then)
|
||||
- support @nosplit pointer arrays?
|
||||
- support pointer to pointer?
|
||||
- really fixing the pointer dereferencing issues (cursed hybrid beween IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator"
|
||||
- (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)
|
||||
|
||||
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- %breakpoint after an assignment is parsed as part of the expression (x % breakpoint), that should not happen
|
||||
- when a complete block is removed because unused, suppress all info messages about everything in the block being removed
|
||||
- fix the line, cols in Position, sometimes they count from 0 sometimes from 1
|
||||
- is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
|
||||
- enums?
|
||||
- romable: should we have a way to explicitly set the memory address for the BSS area (add a -varsaddress and -slabsaddress options?)
|
||||
- romable: fix remaining codegens (some for loops, see ForLoopsAsmGen)
|
||||
- Kotlin: can we use inline value classes in certain spots? (domain types instead of primitives)
|
||||
- add float support to the configurable compiler targets
|
||||
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
|
||||
- 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
|
||||
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
|
||||
- Improve register load order in subroutine call args assignments:
|
||||
in certain situations (need examples!), the "wrong" order of evaluation of function call arguments is done which results
|
||||
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)
|
||||
Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters().
|
||||
- Does it make codegen easier if everything is an expression? Start with the PtProgram ast classes, change statements to expressions that have (new) VOID data type
|
||||
- Can we support signed % (remainder) somehow?
|
||||
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful once we have typed pointers. (addressed in 'struct' branch)
|
||||
- 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)
|
||||
- [much work:] more support for (64tass) SEGMENTS in the prog8 syntax itself?
|
||||
- ability to use a sub instead of only a var for @bank ? what for though? dynamic bank/overlay loading?
|
||||
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions
|
||||
|
||||
|
||||
IR/VM
|
||||
-----
|
||||
- is it possible to use LOADFIELD/STOREFIELD instructions more?
|
||||
- pointer dt's are all reduced to just an uword (in the irTypeString method) - is this okay or could it be beneficial to reintroduce the actual pointer type information? See commit 88b074c208450c58aa32469745afa03e4c5f564a
|
||||
- change the instruction format so an indirect register (a pointer) can be used more often, at least for the inplace assignment operators that operate on pointer
|
||||
- getting it in shape for code generation...: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!)
|
||||
- fix call() return value handling (... what's wrong with it again?)
|
||||
- encode asmsub/extsub clobber info in the call , or maybe include these definitions in the p8ir file itself too. (return registers are already encoded in the CALL instruction)
|
||||
- proper code gen for the CALLI instruction and that it (optionally) returns a word value that needs to be assigned to a reg
|
||||
- implement fast code paths for TODO("inplace split....
|
||||
- implement more TODOs in AssignmentGen
|
||||
- sometimes source lines end up missing in the output p8ir, for example the first assignment is gone in:
|
||||
sub start() {
|
||||
cx16.r0L = cx16.r1 as ubyte
|
||||
cx16.r0sL = cx16.r1s as byte }
|
||||
- do something with the 'split' tag on split word arrays
|
||||
- add more optimizations in IRPeepholeOptimizer
|
||||
- apparently for SSA form, the IRCodeChunk is not a proper "basic block" yet because the last operation should be a branch or return, and no other branches
|
||||
- reduce register usage via linear-scan algorithm (based on live intervals) https://anoopsarkar.github.io/compilers-class/assets/lectures/opt3-regalloc-linearscan.pdf
|
||||
don't forget to take into account the data type of the register when it's going to be reused!
|
||||
- idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
|
||||
global initialization values are simply a list of LOAD instructions.
|
||||
Variables replaced include all subroutine parameters! So the only variables that remain as variables are arrays and strings.
|
||||
- the @split arrays are currently also split in _lsb/_msb arrays in the IR, and operations take multiple (byte) instructions that may lead to verbose and slow operation and machine code generation down the line.
|
||||
maybe another representation is needed once actual codegeneration is done from the IR...?
|
||||
- ExpressionCodeResult: get rid of the separation between single result register and multiple result registers? maybe not, this requires hundreds of lines to change
|
||||
|
||||
|
||||
Libraries
|
||||
---------
|
||||
- Add split-word array sorting routines to sorting module?
|
||||
- See if the raster interrupt handler on the C64 can be tweaked to be a more stable raster irq
|
||||
- pet32 target: make syslib more complete (missing kernal routines)?
|
||||
- need help with: PET disk routines (OPEN, SETLFS etc are not exposed as kernal calls)
|
||||
- c128 target: make syslib more complete (missing kernal routines)?
|
||||
@@ -146,12 +161,13 @@ Libraries
|
||||
Optimizations
|
||||
-------------
|
||||
|
||||
- more optimized operator handling of different types, for example uword a ^ byte b now does a type cast of b to word first
|
||||
- Port benchmarks from https://thred.github.io/c-bench-64/ to prog8 and see how it stacks up.
|
||||
- Since fixing the missing zp-var initialization, programs grew in size again because STZ's reappered. Can we add more intelligent (and correct!) optimizations to remove those STZs that might be redundant again?
|
||||
- Since fixing the missing zp-var initialization, programs grew in size again because STZ's reappeared. Can we add more intelligent (and correct!) optimizations to remove those STZs that might be redundant again?
|
||||
- in Identifier: use typedarray of strings instead of listOf? Other places?
|
||||
- Compilation speed: try to join multiple modifications in 1 result in the AST processors instead of returning it straight away every time
|
||||
- Compare output of some Oscar64 samples to what prog8 does for the equivalent code (see https://github.com/drmortalwombat/OscarTutorials/tree/main and https://github.com/drmortalwombat/oscar64/tree/main/samples)
|
||||
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
|
||||
for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest
|
||||
This will probably need the register categorization from the IR explained there, for the old 6502 codegen there is not enough information to act on
|
||||
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code, those checks should probably be removed, or be made permanent
|
||||
@@ -282,9 +282,10 @@ For instance ``30_000.999_999`` is a valid floating point number 30000.999999.
|
||||
|
||||
Arrays
|
||||
^^^^^^
|
||||
Arrays can be created from a list of booleans, bytes, words, floats, or addresses of other variables
|
||||
(such as explicit address-of expressions, strings, or other array variables) - values in an array literal
|
||||
always have to be constants. A trailing comma is allowed, sometimes this is easier when copying values
|
||||
Arrays can be created from a list of booleans, bytes, words, floats, addresses of other variables
|
||||
(such as explicit address-of expressions, strings, or other array variables), and struct initializers.
|
||||
The values in an array literal always have to be constants.
|
||||
A trailing comma is allowed, sometimes this is easier when copying values
|
||||
or when adding more stuff to the array later. Here are some examples of arrays::
|
||||
|
||||
byte[10] array ; array of 10 bytes, initially set to 0
|
||||
|
||||
@@ -27,11 +27,9 @@ charset {
|
||||
; copies the charset from ROM to RAM so we can modify it
|
||||
|
||||
sys.set_irqd()
|
||||
ubyte bank = @($0001)
|
||||
@($0001) = bank & %11111011 ; enable CHAREN, so the character rom accessible at $d000
|
||||
c64.banks(%011) ; enable CHAREN, so the character rom accessible at $d000
|
||||
sys.memcopy($d000, CHARSET, 256*8*2) ; copy the charset to RAM
|
||||
|
||||
@($0001) = bank ; reset previous memory banking
|
||||
c64.banks(%111) ; enable I/O registers again
|
||||
sys.clear_irqd()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
; TODO in progress attempts to make a flexible sprite multiplexer
|
||||
; For an easy sprite multiplexer (actually, just a sprite duplicator) see the "simplemultiplexer" example.
|
||||
; inspiration: https://codebase64.net/doku.php?id=base:sprite_multiplexing
|
||||
|
||||
%import syslib
|
||||
%import math
|
||||
%import textio
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
const ubyte NUM_VSPRITES = 16
|
||||
const uword spritedata_base = $3000 ; NOTE: in VIC bank 0 (and 2), the Char Rom is at $1000 so we can't store sprite data there
|
||||
|
||||
sub start() {
|
||||
setup_sprite_graphics()
|
||||
c64.SPENA = 255 ; enable all sprites
|
||||
|
||||
method1_wait_raster_before_each_sprite()
|
||||
|
||||
; Theoretically, I think all sprite Y values can be set in one go, and again after the last of the 8 hw sprites has started drawing, etc.
|
||||
; There's no strict need to set it right before the sprite is starting to draw. But it only saves a few cycles?
|
||||
; method2_after_old_sprite()
|
||||
|
||||
}
|
||||
|
||||
sub setup_sprite_graphics() {
|
||||
; prepare the sprite graphics (put a different letter in each of the balloons)
|
||||
sys.set_irqd()
|
||||
c64.banks(%011) ; enable CHAREN, so the character rom accessible at $d000 (inverse at $d400)
|
||||
ubyte i
|
||||
for i in 0 to NUM_VSPRITES-1 {
|
||||
uword sprdat = spritedata_base + $0040*i
|
||||
sys.memcopy(balloonsprite, sprdat, 64)
|
||||
ubyte cptr
|
||||
for cptr in 0 to 7 {
|
||||
sprdat[7+cptr*3] = @($d400+(i+sc:'a')*$0008 + cptr)
|
||||
}
|
||||
^^Sprite sprite = sprites[i]
|
||||
sprite.dataptr = lsb(spritedata_base/64) + i
|
||||
}
|
||||
c64.banks(%111) ; enable IO registers again
|
||||
; sys.clear_irqd() ; do not re-enable IRQ as this will glitch the display because it interferes with raster timing
|
||||
}
|
||||
|
||||
sub method1_wait_raster_before_each_sprite() {
|
||||
repeat {
|
||||
animate_sprites()
|
||||
; wait for raster lines and update hardware sprites
|
||||
ubyte vs_index
|
||||
for vs_index in 0 to NUM_VSPRITES-1 {
|
||||
ubyte virtual_sprite = sort_virtualsprite[vs_index]
|
||||
ubyte hw_sprite = virtual_sprite & 7
|
||||
^^Sprite sprite = sprites[virtual_sprite]
|
||||
sys.waitrasterline(sprite.y-3) ; wait for a few lines above the sprite so we have time to set all attributes properly
|
||||
c64.EXTCOL++
|
||||
c64.SPXY[hw_sprite*2+1] = sprite.y
|
||||
c64.SPXY[hw_sprite*2] = lsb(sprite.x)
|
||||
if sprite.x >= 256
|
||||
c64.MSIGX |= msigx_setmask[hw_sprite]
|
||||
else
|
||||
c64.MSIGX &= msigx_clearmask[hw_sprite]
|
||||
c64.SPCOL[hw_sprite] = sprite.color
|
||||
c64.SPRPTR[hw_sprite] = sprite.dataptr
|
||||
c64.EXTCOL--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ubyte tt
|
||||
sub animate_sprites() {
|
||||
tt += 2
|
||||
ubyte st = tt
|
||||
ubyte virtual_sprite
|
||||
for virtual_sprite in 0 to NUM_VSPRITES-1 {
|
||||
^^Sprite sprite = sprites[virtual_sprite]
|
||||
sprite.x = $0028 + math.sin8u(st)
|
||||
; TODO nice anim sprite.y = $50 + math.cos8u(st*3)/2
|
||||
st += 10
|
||||
sort_ypositions[virtual_sprite] = sprite.y
|
||||
sort_virtualsprite[virtual_sprite] = virtual_sprite
|
||||
; TODO keep working with the previously sorted result instead of rewriting the list every time, makes sorting faster if not much changes in the Y positions
|
||||
}
|
||||
|
||||
; TODO remove this simplistic anim but it's here to test the algorithms
|
||||
sprite = sprites[0]
|
||||
sprite.y++
|
||||
sort_ypositions[0] = sprites[0].y
|
||||
sprite = sprites[1]
|
||||
sprite.y--
|
||||
sort_ypositions[1] = sprites[1].y
|
||||
|
||||
;c64.EXTCOL--
|
||||
sort_virtual_sprites()
|
||||
;c64.EXTCOL++
|
||||
}
|
||||
|
||||
ubyte[NUM_VSPRITES] sort_ypositions
|
||||
ubyte[NUM_VSPRITES] sort_virtualsprite
|
||||
|
||||
; TODO rewrite in assembly, sorting must be super fast
|
||||
sub sort_virtual_sprites() {
|
||||
ubyte @zp pos=1
|
||||
while pos != NUM_VSPRITES {
|
||||
if sort_ypositions[pos]>=sort_ypositions[pos-1]
|
||||
pos++
|
||||
else {
|
||||
; swap elements
|
||||
cx16.r0L = sort_ypositions[pos-1]
|
||||
sort_ypositions[pos-1] = sort_ypositions[pos]
|
||||
sort_ypositions[pos] = cx16.r0L
|
||||
; swap virtual sprite indexes
|
||||
cx16.r0L = sort_virtualsprite[pos-1]
|
||||
sort_virtualsprite[pos-1] = sort_virtualsprite[pos]
|
||||
sort_virtualsprite[pos] = cx16.r0L
|
||||
pos--
|
||||
if_z
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ubyte[8] msigx_setmask = [
|
||||
%00000001,
|
||||
%00000010,
|
||||
%00000100,
|
||||
%00001000,
|
||||
%00010000,
|
||||
%00100000,
|
||||
%01000000,
|
||||
%10000000
|
||||
]
|
||||
|
||||
ubyte[8] msigx_clearmask = [
|
||||
%11111110,
|
||||
%11111101,
|
||||
%11111011,
|
||||
%11110111,
|
||||
%11101111,
|
||||
%11011111,
|
||||
%10111111,
|
||||
%01111111
|
||||
]
|
||||
|
||||
struct Sprite {
|
||||
ubyte color
|
||||
ubyte dataptr
|
||||
uword x
|
||||
ubyte y
|
||||
}
|
||||
|
||||
|
||||
^^Sprite[NUM_VSPRITES] sprites = [
|
||||
[0, $ff, 20, 60],
|
||||
[1, $ff, 40, 70],
|
||||
[2, $ff, 60, 80],
|
||||
[3, $ff, 80, 90],
|
||||
[4, $ff, 100, 100],
|
||||
[5, $ff, 120, 110],
|
||||
[7, $ff, 140, 120], ; skip color 6 (blue on blue)
|
||||
[8, $ff, 160, 130],
|
||||
[9, $ff, 180, 140],
|
||||
[10, $ff, 200, 150],
|
||||
[11, $ff, 220, 160],
|
||||
[12, $ff, 240, 170],
|
||||
[13, $ff, 260, 180],
|
||||
[14, $ff, 280, 190],
|
||||
[15, $ff, 300, 200],
|
||||
[0, $ff, 320, 210],
|
||||
]
|
||||
|
||||
|
||||
ubyte[] balloonsprite = [
|
||||
%00000000,%01111111,%00000000,
|
||||
%00000001,%11111111,%11000000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000111,%11011101,%11110000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000010,%11111111,%10100000,
|
||||
%00000001,%01111111,%01000000,
|
||||
%00000001,%00111110,%01000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00011100,%00000000
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
; Simple sprite multiplexer.
|
||||
; This is just a sprite duplicator routine that displays the 8 hardware sprites multiple times in rows,
|
||||
; it does not do any complicated sprite Y manipulation/multiplexing.
|
||||
; No assembly code is used.
|
||||
|
||||
|
||||
%import syslib
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in 0 to 7 {
|
||||
c64.set_sprite_ptr(i, &sprites.balloonsprite) ; alternatively, set directly: c64.SPRPTR[i] = $0a00 / 64
|
||||
}
|
||||
sprites.set_sprites_X(0)
|
||||
sprites.set_sprites_Y(sprites.sprites_Y_start)
|
||||
c64.SPCOL[5] = 8 ; change blue balloon to different color
|
||||
|
||||
c64.SPENA = 255 ; enable all sprites
|
||||
irq.first_sprite_X = 0
|
||||
irq.sprites_Y = sprites.sprites_Y_start
|
||||
sys.set_rasterirq(&irq.multiplexer, irq.sprites_Y+1)
|
||||
|
||||
; exit program back to basic, balloons will keep flying :-)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
irq {
|
||||
uword first_sprite_X
|
||||
ubyte sprites_Y
|
||||
|
||||
; Here is the actual multiplexing routine.
|
||||
; it's a raster irq just after the start of the sprite,
|
||||
; that updates the Y position of all the sprits,
|
||||
; and registers a new rater irq for that next row of sprites.
|
||||
; If the bottom of the screen is reached, it resets the X position of the sprites as well,
|
||||
; and moves the sprites back to the top of the screen.
|
||||
sub multiplexer() -> bool {
|
||||
c64.EXTCOL++
|
||||
sprites_Y += 24
|
||||
bool system_irq = false
|
||||
if sprites_Y > (255-24-1) {
|
||||
cx16.r2 = c64.RASTER + 23
|
||||
sprites_Y = sprites.sprites_Y_start
|
||||
first_sprite_X++
|
||||
if first_sprite_X >= 340
|
||||
first_sprite_X =0
|
||||
sprites.set_sprites_Y(sprites_Y)
|
||||
while c64.RASTER != cx16.r2 {
|
||||
; wait until raster line after sprite has been fully drawn (at least 24 lines down)
|
||||
}
|
||||
sprites.set_sprites_X(first_sprite_X) ; we can now update the X positions without risk of sprite tearing
|
||||
system_irq = true
|
||||
} else {
|
||||
sprites.set_sprites_Y(sprites_Y)
|
||||
}
|
||||
|
||||
sys.set_rasterline(sprites_Y+1)
|
||||
c64.EXTCOL--
|
||||
return system_irq
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sprites {
|
||||
const ubyte sprites_Y_start = 55
|
||||
|
||||
sub set_sprites_Y(ubyte y) {
|
||||
for cx16.r0L in 1 to 15 step 2 {
|
||||
c64.SPXY[cx16.r0L] = y
|
||||
}
|
||||
}
|
||||
|
||||
sub set_sprites_X(uword x) {
|
||||
for cx16.r0L in 0 to 14 step 2 {
|
||||
x += 20
|
||||
if x >= 340
|
||||
x -= 340
|
||||
c64.SPXY[cx16.r0L] = lsb(x)
|
||||
cx16.r1L = msb(x)>>1
|
||||
ror(c64.MSIGX) ; rotate that MSB bit of the sprite X position into the MSB sprite X register for each sprite
|
||||
}
|
||||
}
|
||||
|
||||
ubyte[] @align64 balloonsprite = [
|
||||
%00000000,%01111111,%00000000,
|
||||
%00000001,%11111111,%11000000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000111,%11011101,%11110000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000010,%11111111,%10100000,
|
||||
%00000001,%01111111,%01000000,
|
||||
%00000001,%00111110,%01000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00011100,%00000000
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,17 +7,17 @@ main {
|
||||
|
||||
sub start() {
|
||||
|
||||
txt.print("balloon sprites!\n...we are all floating...\n")
|
||||
txt.print("balloon sprites!\n...we are all floating...\nborders are open too\n")
|
||||
|
||||
ubyte @zp i
|
||||
for i in 0 to 7 {
|
||||
c64.set_sprite_ptr(i, &spritedata.balloonsprite) ; alternatively, set directly: c64.SPRPTR[i] = $0a00 / 64
|
||||
c64.SPXY[i*2] = 50+25*i
|
||||
c64.SPXY[i*2] = 60+22*i
|
||||
c64.SPXY[i*2+1] = math.rnd()
|
||||
}
|
||||
|
||||
c64.SPENA = 255 ; enable all sprites
|
||||
sys.set_rasterirq(&irq.irqhandler, 255) ; enable animation
|
||||
c64.SPENA = 255 ; enable all sprites
|
||||
sys.set_rasterirq(&irq.irqhandler, 248) ; trigger irq just above bottom border line
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ main {
|
||||
irq {
|
||||
|
||||
sub irqhandler() -> bool {
|
||||
c64.SCROLY = 19 ; 24 row mode, preparing for border opening
|
||||
c64.EXTCOL--
|
||||
|
||||
; float up & wobble horizontally
|
||||
@@ -39,6 +40,7 @@ irq {
|
||||
}
|
||||
|
||||
c64.EXTCOL++
|
||||
c64.SCROLY = 27 ; 25 row mode, border is open
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ more example materials.
|
||||
|
||||
Look in the Makefile to see how to build or run the various programs.
|
||||
|
||||
The user 'adiee5' has been working on a Nintendo Entertainment System (NES) compilation target
|
||||
*adiee5* has been working on a Nintendo Entertainment System (NES) compilation target
|
||||
and example program, you can find those efforts here on GitHub: https://github.com/adiee5/prog8-nes-target
|
||||
Note that the NES is a very alien architecture for Prog8 still and the support is very limited
|
||||
(for example, prog8 is not aware that the program code usually is going to end up in a ROM cartridge,
|
||||
and currently still generates code that might not work in ROM.)
|
||||
|
||||
*gillham* has been working on a few other compilation targets, such as VIC-20 (various editions), Foenix, and CX16OS.
|
||||
you can find them here on GitHub: https://github.com/gillham/prog8targets
|
||||
@@ -0,0 +1,113 @@
|
||||
|
||||
; color fader that doesn't do palette fade but color attribute fade.
|
||||
; NOTE: this needs ROM 49+
|
||||
|
||||
%import textio
|
||||
%import math
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
|
||||
main {
|
||||
^^uword palette = memory("palette", 256*2, 0)
|
||||
^^ubyte closest = memory("closest", 4096, 0)
|
||||
|
||||
const ubyte WIDTH = 40
|
||||
const ubyte HEIGHT = 30
|
||||
|
||||
sub start() {
|
||||
|
||||
fill_default_palette()
|
||||
|
||||
txt.print("\nprecalc color fade table, patience plz ")
|
||||
precalc_fade_table()
|
||||
|
||||
cx16.set_screen_mode(128)
|
||||
cx16.GRAPH_set_colors(0,0,0)
|
||||
cx16.GRAPH_clear()
|
||||
cx16.VERA_L1_CONFIG |= %1000 ; T256C on!
|
||||
ubyte x,y
|
||||
for y in 0 to HEIGHT-1 {
|
||||
for x in 0 to WIDTH-1 {
|
||||
txt.setcc2(x,y, math.rnd(), math.rnd())
|
||||
}
|
||||
}
|
||||
|
||||
sys.wait(60)
|
||||
|
||||
repeat {
|
||||
repeat 4 sys.waitvsync()
|
||||
|
||||
for y in 0 to HEIGHT-1 {
|
||||
for x in 0 to WIDTH-1 {
|
||||
ubyte @zp currentcolor = txt.getclr(x,y)
|
||||
cx16.r0L = closest[darken(palette[currentcolor])]
|
||||
if cx16.r0L>0 and cx16.r0L == currentcolor
|
||||
cx16.r0L = 15 ; to avoid stuck colors, make it gray, this will be able to fade to black because the pallette contains all shades of gray.
|
||||
txt.setclr(x,y, cx16.r0L)
|
||||
}
|
||||
|
||||
txt.setcc2(math.rnd() % WIDTH, math.rnd() % HEIGHT, math.rnd(), math.rnd()) ; add new chars
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub precalc_fade_table() {
|
||||
uword colorindex
|
||||
ubyte r,g,b
|
||||
alias color = cx16.r2
|
||||
|
||||
for r in 0 to 15 {
|
||||
txt.print_ub(16-r)
|
||||
txt.spc()
|
||||
for g in 0 to 15 {
|
||||
for b in 0 to 15 {
|
||||
color = mkword(r, g<<4 | b)
|
||||
closest[colorindex] = find_closest(color)
|
||||
colorindex++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub darken(uword color @R0) -> uword {
|
||||
if cx16.r0H!=0
|
||||
cx16.r0H--
|
||||
if cx16.r0L & $0f != 0
|
||||
cx16.r0L--
|
||||
if cx16.r0L & $f0 != 0
|
||||
cx16.r0L -= $10
|
||||
return cx16.r0
|
||||
}
|
||||
|
||||
sub find_closest(uword rgb @R0) -> ubyte {
|
||||
ubyte distance = 255
|
||||
ubyte current = 0
|
||||
alias index = cx16.r1L
|
||||
|
||||
for index in 0 to 255 {
|
||||
uword @zp pc = palette[index]
|
||||
ubyte @zp d2 = abs(msb(pc) as byte - msb(rgb) as byte) ; RED distance
|
||||
d2 += abs((lsb(pc) & $f0) as byte - (lsb(rgb) & $f0) as byte) >> 4 ; GREEN distance
|
||||
d2 += abs((lsb(pc) & $0f) as byte - (lsb(rgb) & $0f) as byte) ; BLUE distance
|
||||
if d2==0
|
||||
return index
|
||||
if d2 < distance {
|
||||
distance = d2
|
||||
current = index
|
||||
}
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
sub fill_default_palette() {
|
||||
ubyte pal_bank
|
||||
uword pal_addr
|
||||
|
||||
pal_bank, pal_addr = cx16.get_default_palette() ; needs ROM 49+
|
||||
cx16.push_rombank(pal_bank)
|
||||
sys.memcopy(pal_addr, palette, 256*2)
|
||||
cx16.pop_rombank()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
; Doubly Linked List with Hash Table Cache
|
||||
; Demonstrates advanced pointer usage with a combination of doubly linked lists and hash tables
|
||||
; for efficient data storage and retrieval
|
||||
|
||||
|
||||
%import strings
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
txt.print("Doubly Linked List with Hash Table Cache Demo\n")
|
||||
txt.print("============================================\n\n")
|
||||
|
||||
; Initialize the data structure
|
||||
cache.init()
|
||||
|
||||
; Add some sample data
|
||||
txt.print("Adding sample data...\n")
|
||||
cache.add("Alice", "Engineer", 30)
|
||||
cache.add("Bob", "Designer", 25)
|
||||
cache.add("Charlie", "Manager", 35)
|
||||
cache.add("Diana", "Developer", 28)
|
||||
cache.add("Eve", "Analyst", 32)
|
||||
|
||||
txt.print("\nForward traversal:\n")
|
||||
cache.print_forward()
|
||||
|
||||
txt.print("\nBackward traversal:\n")
|
||||
cache.print_backward()
|
||||
|
||||
txt.print("\nSearching for specific entries:\n")
|
||||
txt.print("Looking for 'Charlie': ")
|
||||
^^cache.Entry entry = cache.find("Charlie")
|
||||
if entry!=0 {
|
||||
txt.print(entry.name)
|
||||
txt.print(" - ")
|
||||
txt.print(entry.job)
|
||||
txt.print(" (")
|
||||
txt.print_ub(entry.age)
|
||||
txt.print(" years old)\n")
|
||||
} else {
|
||||
txt.print("Not found\n")
|
||||
}
|
||||
|
||||
txt.print("Looking for 'Frank': ")
|
||||
entry = cache.find("Frank")
|
||||
if entry!=0 {
|
||||
txt.print("Found\n")
|
||||
} else {
|
||||
txt.print("Not found\n")
|
||||
}
|
||||
|
||||
txt.print("\nRemoving 'Bob'...\n")
|
||||
void cache.remove("Bob")
|
||||
|
||||
txt.print("Forward traversal after removal:\n")
|
||||
cache.print_forward()
|
||||
|
||||
txt.print("\nDemonstrating hash collision handling...\n")
|
||||
; Add entries that will likely collide in the hash table
|
||||
cache.add("A", "First", 20)
|
||||
cache.add("B", "Second", 21)
|
||||
cache.add("C", "Third", 22)
|
||||
cache.add("D", "Fourth", 23)
|
||||
cache.add("E", "Fifth", 24)
|
||||
|
||||
txt.print("Added entries with potential hash collisions.\n")
|
||||
txt.print("Forward traversal:\n")
|
||||
cache.print_forward()
|
||||
}
|
||||
}
|
||||
|
||||
cache {
|
||||
struct Entry {
|
||||
^^Entry next ; Next entry in the list
|
||||
^^Entry prev ; Previous entry in the list
|
||||
^^Entry hash_next ; Next entry in the hash bucket
|
||||
str name ; Name (key)
|
||||
str job ; Job description
|
||||
ubyte age ; Age
|
||||
}
|
||||
|
||||
const ubyte HASH_TABLE_SIZE = 16
|
||||
^^Entry[HASH_TABLE_SIZE] hash_table ; Hash table for fast lookups
|
||||
^^Entry head = 0 ; Head of the doubly linked list
|
||||
^^Entry tail = 0 ; Tail of the doubly linked list
|
||||
uword count = 0 ; Number of entries
|
||||
|
||||
sub init() {
|
||||
; Initialize hash table buckets to null
|
||||
sys.memsetw(hash_table, HASH_TABLE_SIZE, 0)
|
||||
}
|
||||
|
||||
sub add(str name, str job, ubyte age) {
|
||||
; Create new entry
|
||||
^^Entry new_entry = arena.alloc(sizeof(Entry))
|
||||
^^ubyte name_copy = arena.alloc(strings.length(name) + 1)
|
||||
^^ubyte job_copy = arena.alloc(strings.length(job) + 1)
|
||||
void strings.copy(name, name_copy)
|
||||
void strings.copy(job, job_copy)
|
||||
|
||||
new_entry.name = name_copy
|
||||
new_entry.job = job_copy
|
||||
new_entry.age = age
|
||||
new_entry.next = 0
|
||||
new_entry.prev = 0
|
||||
new_entry.hash_next = 0
|
||||
|
||||
; Add to the end of the doubly linked list
|
||||
if head == 0 {
|
||||
; First entry
|
||||
head = new_entry
|
||||
tail = new_entry
|
||||
} else {
|
||||
; Add to the end
|
||||
tail.next = new_entry
|
||||
new_entry.prev = tail
|
||||
tail = new_entry
|
||||
}
|
||||
|
||||
; Add to hash table
|
||||
ubyte bucket = strings.hash(name) % HASH_TABLE_SIZE
|
||||
new_entry.hash_next = hash_table[bucket]
|
||||
hash_table[bucket] = new_entry
|
||||
|
||||
count++
|
||||
txt.print("Added: ")
|
||||
txt.print(name)
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub find(str name) -> ^^Entry {
|
||||
; Find entry using hash table for O(1) average case
|
||||
ubyte bucket = strings.hash(name) % HASH_TABLE_SIZE
|
||||
^^Entry current = hash_table[bucket]
|
||||
|
||||
while current != 0 {
|
||||
if strings.compare(current.name, name) == 0
|
||||
return current
|
||||
current = current.hash_next
|
||||
}
|
||||
|
||||
return 0 ; Not found
|
||||
}
|
||||
|
||||
sub remove(str name) -> bool {
|
||||
; Find the entry
|
||||
^^Entry to_remove = find(name)
|
||||
if to_remove == 0
|
||||
return false ; Not found
|
||||
|
||||
; Remove from doubly linked list
|
||||
if to_remove.prev != 0
|
||||
to_remove.prev.next = to_remove.next
|
||||
else
|
||||
head = to_remove.next ; Was the head
|
||||
|
||||
if to_remove.next != 0
|
||||
to_remove.next.prev = to_remove.prev
|
||||
else
|
||||
tail = to_remove.prev ; Was the tail
|
||||
|
||||
; Remove from hash table
|
||||
ubyte bucket = strings.hash(name) % HASH_TABLE_SIZE
|
||||
if hash_table[bucket] == to_remove {
|
||||
hash_table[bucket] = to_remove.hash_next
|
||||
} else {
|
||||
^^Entry current = hash_table[bucket]
|
||||
while current.hash_next != 0 {
|
||||
if current.hash_next == to_remove {
|
||||
current.hash_next = to_remove.hash_next
|
||||
break
|
||||
}
|
||||
current = current.hash_next
|
||||
}
|
||||
}
|
||||
|
||||
count--
|
||||
txt.print("Removed: ")
|
||||
txt.print(name)
|
||||
txt.print("\n")
|
||||
return true
|
||||
}
|
||||
|
||||
sub print_forward() {
|
||||
^^Entry current = head
|
||||
while current != 0 {
|
||||
txt.print("- ")
|
||||
txt.print(current.name)
|
||||
txt.print(" (")
|
||||
txt.print(current.job)
|
||||
txt.print(", ")
|
||||
txt.print_ub(current.age)
|
||||
txt.print(")\n")
|
||||
current = current.next
|
||||
}
|
||||
txt.print("Total entries: ")
|
||||
txt.print_uw(count)
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub print_backward() {
|
||||
^^Entry current = tail
|
||||
while current != 0 {
|
||||
txt.print("- ")
|
||||
txt.print(current.name)
|
||||
txt.print(" (")
|
||||
txt.print(current.job)
|
||||
txt.print(", ")
|
||||
txt.print_ub(current.age)
|
||||
txt.print(")\n")
|
||||
current = current.prev
|
||||
}
|
||||
txt.print("Total entries: ")
|
||||
txt.print_uw(count)
|
||||
txt.print("\n")
|
||||
}
|
||||
}
|
||||
|
||||
arena {
|
||||
; Simple arena allocator
|
||||
uword buffer = memory("arena", 8000, 0)
|
||||
uword next = buffer
|
||||
|
||||
sub alloc(ubyte size) -> uword {
|
||||
defer next += size
|
||||
return next
|
||||
}
|
||||
}
|
||||
@@ -11,34 +11,32 @@ main{
|
||||
uword area ; 1000 km^2
|
||||
}
|
||||
|
||||
^^Country[100] countries ; won't be fully filled
|
||||
ubyte num_countries
|
||||
^^Country[] countries = [
|
||||
^^Country:["Indonesia", 285.72, 1904],
|
||||
^^Country:["Congo", 112.83, 2344],
|
||||
^^Country:["Vietnam", 101.60, 331],
|
||||
^^Country:["United States", 347.28, 9372],
|
||||
^^Country:["Iran", 92.42, 1648],
|
||||
^^Country:["Turkey", 87.69, 783],
|
||||
^^Country:["Brazil", 212.81, 8515],
|
||||
^^Country:["Bangladesh", 175.69, 147],
|
||||
^^Country:["Germany", 84.08, 357],
|
||||
^^Country:["Japan", 123.10, 377],
|
||||
^^Country:["India", 1463.87, 3287],
|
||||
^^Country:["China", 1416.10, 9596],
|
||||
^^Country:["Philippines", 116.79, 300],
|
||||
^^Country:["Russia", 143.99, 17098],
|
||||
^^Country:["Pakistan", 255.22, 881],
|
||||
^^Country:["Nigeria", 237.53, 923],
|
||||
^^Country:["Ethiopia", 135.47, 1104],
|
||||
^^Country:["Mexico", 131.95, 1964],
|
||||
^^Country:["Thailand", 71.62, 513],
|
||||
^^Country:["Egypt", 118.37, 1002],
|
||||
]
|
||||
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
|
||||
; because pointer array initialization is not supported yet, we have to add the countries in separate statements for now
|
||||
add(^^Country:["Indonesia", 285.72, 1904])
|
||||
add(^^Country:["Congo", 112.83, 2344])
|
||||
add(^^Country:["Vietnam", 101.60, 331])
|
||||
add(^^Country:["United States", 347.28, 9372])
|
||||
add(^^Country:["Iran", 92.42, 1648])
|
||||
add(^^Country:["Turkey", 87.69, 783])
|
||||
add(^^Country:["Brazil", 212.81, 8515])
|
||||
add(^^Country:["Bangladesh", 175.69, 147])
|
||||
add(^^Country:["Germany", 84.08, 357])
|
||||
add(^^Country:["Japan", 123.10, 377])
|
||||
add(^^Country:["India", 1463.87, 3287])
|
||||
add(^^Country:["China", 1416.10, 9596])
|
||||
add(^^Country:["Philippines", 116.79, 300])
|
||||
add(^^Country:["Russia", 143.99, 17098])
|
||||
add(^^Country:["Pakistan", 255.22, 881])
|
||||
add(^^Country:["Nigeria", 237.53, 923])
|
||||
add(^^Country:["Ethiopia", 135.47, 1104])
|
||||
add(^^Country:["Mexico", 131.95, 1964])
|
||||
add(^^Country:["Thailand", 71.62, 513])
|
||||
add(^^Country:["Egypt", 118.37, 1002])
|
||||
|
||||
txt.print("UNSORTED:\n")
|
||||
dump()
|
||||
|
||||
@@ -57,7 +55,7 @@ main{
|
||||
|
||||
sub sort_by_name() {
|
||||
; stupid slow bubble sort
|
||||
ubyte n = num_countries
|
||||
ubyte n = len(countries)
|
||||
do {
|
||||
ubyte newn=0
|
||||
ubyte i
|
||||
@@ -73,7 +71,7 @@ main{
|
||||
|
||||
sub sort_by_population() {
|
||||
; stupid slow bubble sort
|
||||
ubyte n = num_countries
|
||||
ubyte n = len(countries)
|
||||
do {
|
||||
ubyte newn=0
|
||||
ubyte i
|
||||
@@ -89,7 +87,7 @@ main{
|
||||
|
||||
sub sort_by_area() {
|
||||
; stupid slow bubble sort
|
||||
ubyte n = num_countries
|
||||
ubyte n = len(countries)
|
||||
do {
|
||||
ubyte newn=0
|
||||
ubyte i
|
||||
@@ -125,10 +123,5 @@ main{
|
||||
txt.nl()
|
||||
}
|
||||
}
|
||||
|
||||
sub add(^^Country c) {
|
||||
countries[num_countries] = c
|
||||
num_countries++
|
||||
}
|
||||
}
|
||||
|
||||
+10
-37
@@ -1,42 +1,15 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
sys.set_carry()
|
||||
txt.print_ub(if_cc 0 else 1)
|
||||
txt.nl()
|
||||
sys.clear_carry()
|
||||
txt.print_ub(if_cc 0 else 1)
|
||||
txt.nl()
|
||||
sys.set_carry()
|
||||
txt.print_ub(if_cs 0 else 1)
|
||||
txt.nl()
|
||||
sys.clear_carry()
|
||||
txt.print_ub(if_cs 0 else 1)
|
||||
txt.nl()
|
||||
sprptr[2]^^.y++
|
||||
}
|
||||
|
||||
struct Sprite {
|
||||
ubyte x
|
||||
uword y
|
||||
}
|
||||
|
||||
^^Sprite[4] @shared sprites
|
||||
^^Sprite @shared sprptr
|
||||
}
|
||||
|
||||
;%import textio
|
||||
;%zeropage basicsafe
|
||||
;
|
||||
;main {
|
||||
; struct Node {
|
||||
; ubyte id
|
||||
; str name
|
||||
; uword array
|
||||
; }
|
||||
;
|
||||
; ^^Node @shared @zp node = 2000
|
||||
;
|
||||
; sub start() {
|
||||
; txt.print_uw(node)
|
||||
; txt.spc()
|
||||
; cx16.r0 = &node.array ; TODO don't clobber node pointer itself!!
|
||||
; txt.print_uw(cx16.r0)
|
||||
; txt.spc()
|
||||
; txt.print_uw(node)
|
||||
; txt.nl()
|
||||
; }
|
||||
;}
|
||||
+1
-1
@@ -3,4 +3,4 @@ org.gradle.console=rich
|
||||
org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
kotlin.code.style=official
|
||||
version=12.0-BETA1
|
||||
version=12.0-BETA3
|
||||
@@ -241,8 +241,10 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
if(floats) it.number.toString()
|
||||
else it.number.toInt().toHex()
|
||||
}
|
||||
else
|
||||
else if(it.addressOfSymbol!=null)
|
||||
"@${it.addressOfSymbol}"
|
||||
else
|
||||
throw InternalCompilerException("weird array value")
|
||||
}
|
||||
|
||||
private fun writeVariables() {
|
||||
@@ -283,14 +285,18 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
lsbValue = variable.onetimeInitializationArrayValue.joinToString(",") {
|
||||
if(it.number!=null)
|
||||
(it.number.toInt() and 255).toHex()
|
||||
else
|
||||
else if (it.addressOfSymbol!=null)
|
||||
"@<${it.addressOfSymbol}"
|
||||
else
|
||||
throw InternalCompilerException("weird array value")
|
||||
}
|
||||
msbValue = variable.onetimeInitializationArrayValue.joinToString(",") {
|
||||
if(it.number!=null)
|
||||
(it.number.toInt() shr 8).toHex()
|
||||
else
|
||||
else if(it.addressOfSymbol!=null)
|
||||
"@>${it.addressOfSymbol}"
|
||||
else
|
||||
throw InternalCompilerException("weird array value")
|
||||
}
|
||||
}
|
||||
xml.writeCharacters("ubyte[${variable.length}] ${variable.name}_lsb=$lsbValue zp=${variable.zpwish} split=true")
|
||||
@@ -388,8 +394,10 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
value.dt.isInteger || value.dt.isPointer -> {
|
||||
if(value.value.number!=null)
|
||||
value.value.number.toInt().toHex()
|
||||
else
|
||||
else if(value.value.addressOfSymbol!=null)
|
||||
"@${value.value.addressOfSymbol}"
|
||||
else
|
||||
throw InternalCompilerException("weird field value")
|
||||
}
|
||||
value.dt == BaseDataType.FLOAT -> {
|
||||
value.value.number.toString()
|
||||
|
||||
@@ -973,7 +973,7 @@ data class IRInstruction(
|
||||
when (this.reg2direction) {
|
||||
OperandDirection.UNUSED -> {}
|
||||
OperandDirection.READ -> {
|
||||
writeRegsCounts[this.reg2!!] = writeRegsCounts.getValue(this.reg2)+1
|
||||
readRegsCounts[this.reg2!!] = readRegsCounts.getValue(this.reg2)+1
|
||||
val actualtype = determineReg2Type()
|
||||
if(actualtype!=null) {
|
||||
val existingType = regsTypes[reg2]
|
||||
@@ -989,7 +989,7 @@ data class IRInstruction(
|
||||
when (this.reg3direction) {
|
||||
OperandDirection.UNUSED -> {}
|
||||
OperandDirection.READ -> {
|
||||
writeRegsCounts[this.reg3!!] = writeRegsCounts.getValue(this.reg3)+1
|
||||
readRegsCounts[this.reg3!!] = readRegsCounts.getValue(this.reg3)+1
|
||||
val actualtype = determineReg3Type()
|
||||
if(actualtype!=null) {
|
||||
val existingType = regsTypes[reg3]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package prog8.intermediate
|
||||
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.PROG8_CONTAINER_MODULES
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
@@ -49,10 +49,9 @@ class IRSymbolTable {
|
||||
val prefix = "$label."
|
||||
val vars = table.filter { it.key.startsWith(prefix) }
|
||||
vars.forEach {
|
||||
// check if attempt is made to delete interned strings, if so, refuse that.
|
||||
if(!it.key.startsWith(INTERNED_STRINGS_MODULENAME)) {
|
||||
// check if attempt is made to delete fixed modules, if so, refuse that.
|
||||
if(!PROG8_CONTAINER_MODULES.any { containername -> it.key.startsWith(containername)})
|
||||
table.remove(it.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +126,10 @@ class IRStArrayElement(val bool: Boolean?, val number: Double?, val addressOfSym
|
||||
init {
|
||||
if(bool!=null) require(number==null && addressOfSymbol==null)
|
||||
if(number!=null) require(bool==null && addressOfSymbol==null)
|
||||
if(addressOfSymbol!=null) require(number==null || bool==null)
|
||||
if(addressOfSymbol!=null) {
|
||||
require(number==null || bool==null)
|
||||
require('.' in addressOfSymbol) { "addressOfSymbol must be a scoped name" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +137,7 @@ class IRStStructDef(name: String, val fields: List<Pair<DataType, String>>, val
|
||||
|
||||
class IRStStructInstance(name: String, val structName: String, val values: List<IRStructInitValue>, val size: UInt): IRStNode(name, IRStNodeType.STRUCTINSTANCE) {
|
||||
init {
|
||||
require('.' in structName)
|
||||
require('.' in structName) { "structName must be a scoped name" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ labeldef : identifier ':' ;
|
||||
|
||||
unconditionaljump : GOTO expression ;
|
||||
|
||||
directive : directivename (directivenamelist | (directivearg? | directivearg (',' directivearg)*)) ;
|
||||
directive : directivename '!'? (directivenamelist | (directivearg? | directivearg (',' directivearg)*)) ;
|
||||
|
||||
directivename: '%' UNICODEDNAME;
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
|
||||
val scopehash = call.parent.hashCode().toUInt().toString(16)
|
||||
val pos = "${call.position.line}_${call.position.startCol}"
|
||||
val hash = call.position.file.hashCode().toUInt().toString(16)
|
||||
return "prog8_struct_${structname.replace('.', '_')}_${hash}_${pos}_${scopehash}"
|
||||
return "${structname.replace('.', '_')}_${hash}_${scopehash}_${pos}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,13 +338,18 @@ class StExtSub(name: String,
|
||||
|
||||
class StSubroutineParameter(val name: String, val type: DataType, val register: RegisterOrPair?)
|
||||
class StExtSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
||||
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?) {
|
||||
class StArrayElement(val number: Double?, val addressOfSymbol: String?, val structInstance: String?, val structInstanceUninitialized: String?, val boolean: Boolean?) {
|
||||
init {
|
||||
if(number!=null) require(addressOfSymbol==null && boolean==null)
|
||||
if(addressOfSymbol!=null) require(number==null && boolean==null)
|
||||
if(boolean!=null) require(addressOfSymbol==null && number==null)
|
||||
if(number!=null) require(addressOfSymbol==null && boolean==null && structInstance==null && structInstanceUninitialized==null)
|
||||
if(addressOfSymbol!=null) require(number==null && boolean==null && structInstance==null && structInstanceUninitialized==null)
|
||||
if(structInstance!=null) require(number==null && boolean==null && addressOfSymbol==null && structInstanceUninitialized==null)
|
||||
if(structInstanceUninitialized!=null) require(number==null && boolean==null && addressOfSymbol==null && structInstance==null)
|
||||
if(boolean!=null) require(addressOfSymbol==null && number==null &&structInstance==null && structInstanceUninitialized==null)
|
||||
}
|
||||
}
|
||||
|
||||
typealias StString = Pair<String, Encoding>
|
||||
typealias StArray = List<StArrayElement>
|
||||
|
||||
const val StMemorySlabBlockName = "prog8_slabs"
|
||||
const val StStructInstanceBlockName = "prog8_struct_instances"
|
||||
@@ -82,7 +82,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
numElements = (value.value.length + 1).toUInt() // include the terminating 0-byte
|
||||
}
|
||||
is PtArray -> {
|
||||
initialArray = makeInitialArray(value)
|
||||
initialArray = makeInitialArray(value, scope)
|
||||
initialString = null
|
||||
initialNumeric = null
|
||||
numElements = initialArray.size.toUInt()
|
||||
@@ -119,22 +119,12 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
val size = (node.args[1] as PtNumber).number.toUInt()
|
||||
val align = (node.args[2] as PtNumber).number.toUInt()
|
||||
// don't add memory slabs in nested scope, just put them in the top level of the ST
|
||||
scope.first().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
|
||||
scope.first().add(StMemorySlab("memory_$slabname", size, align, node))
|
||||
}
|
||||
else if(node.name=="prog8_lib_structalloc") {
|
||||
val struct = node.type.subType!!
|
||||
if(struct is StStruct) {
|
||||
val label = SymbolTable.labelnameForStructInstance(node)
|
||||
val initialValues = node.args.map {
|
||||
when(it) {
|
||||
is PtAddressOf -> StArrayElement(null, it.identifier!!.name, null)
|
||||
is PtBool -> StArrayElement(null, null, it.value)
|
||||
is PtNumber -> StArrayElement(it.number, null, null)
|
||||
else -> throw AssemblyError("invalid structalloc argument type $it")
|
||||
}
|
||||
}
|
||||
val scopedName = if(struct.astNode!=null) (struct.astNode as PtNamedNode).scopedName else struct.scopedNameString
|
||||
scope.first().add(StStructInstance(label, scopedName, initialValues, struct.size, null))
|
||||
val instance = handleStructAllocation(node)
|
||||
if(instance!=null) {
|
||||
scope.first().add(instance) // don't add struct instances in nested scope, just put them in the top level of the ST
|
||||
}
|
||||
}
|
||||
null
|
||||
@@ -153,20 +143,50 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
scope.removeLast()
|
||||
}
|
||||
|
||||
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
|
||||
private fun handleStructAllocation(node: PtBuiltinFunctionCall): StStructInstance? {
|
||||
val struct = node.type.subType as? StStruct ?: return null
|
||||
val initialValues = node.args.map {
|
||||
when(it) {
|
||||
is PtAddressOf -> StArrayElement(null, it.identifier!!.name, null, null,null)
|
||||
is PtBool -> StArrayElement(null, null, null, null, it.value)
|
||||
is PtNumber -> StArrayElement(it.number, null, null, null, null)
|
||||
else -> throw AssemblyError("invalid structalloc argument type $it")
|
||||
}
|
||||
}
|
||||
val label = SymbolTable.labelnameForStructInstance(node)
|
||||
val scopedStructName = if(struct.astNode!=null) (struct.astNode as PtNamedNode).scopedName else struct.scopedNameString
|
||||
return StStructInstance(label, scopedStructName, initialValues, struct.size, null)
|
||||
}
|
||||
|
||||
private fun makeInitialArray(value: PtArray, scope: ArrayDeque<StNode>): List<StArrayElement> {
|
||||
return value.children.map {
|
||||
when(it) {
|
||||
is PtAddressOf -> {
|
||||
when {
|
||||
it.isFromArrayElement -> TODO("address-of array element $it in initial array value")
|
||||
else -> StArrayElement(null, it.identifier!!.name, null)
|
||||
else -> StArrayElement(null, it.identifier!!.name, null, null,null)
|
||||
}
|
||||
}
|
||||
is PtNumber -> StArrayElement(it.number, null, null)
|
||||
is PtBool -> StArrayElement(null, null, it.value)
|
||||
is PtNumber -> StArrayElement(it.number, null, null,null,null)
|
||||
is PtBool -> StArrayElement(null, null, null,null,it.value)
|
||||
is PtBuiltinFunctionCall -> {
|
||||
val labelname = SymbolTable.labelnameForStructInstance(it)
|
||||
StArrayElement(null, labelname, null)
|
||||
if(it.name=="prog8_lib_structalloc") {
|
||||
val instance = handleStructAllocation(it)
|
||||
if(instance==null) {
|
||||
val label = SymbolTable.labelnameForStructInstance(it)
|
||||
if (it.args.isEmpty())
|
||||
StArrayElement(null, null, null, label, null)
|
||||
else
|
||||
StArrayElement(null, null, label, null, null)
|
||||
} else {
|
||||
scope.first().add(instance) // don't add struct instances in nested scope, just put them in the top level of the ST
|
||||
if (it.args.isEmpty())
|
||||
StArrayElement(null, null, null, instance.name, null)
|
||||
else
|
||||
StArrayElement(null, null, instance.name, null, null)
|
||||
}
|
||||
} else
|
||||
TODO("support for initial array element via ${it.name} ${it.position}")
|
||||
}
|
||||
else -> throw AssemblyError("invalid array element $it")
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<keywords keywords="&;&&;&<;&>;->;@;^^;alias;and;as;asmsub;break;clobbers;continue;do;downto;else;extsub;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;on;or;repeat;return;step;sub;to;true;unroll;until;when;while;xor;~" ignore_case="false" />
|
||||
<keywords2 keywords="%address;%align;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%jmptable;%launcher;%memtop;%option;%output;%zeropage;%zpallowed;%zpreserved;@align64;@alignpage;@alignword;@bank;@dirty;@nosplit;@nozp;@requirezp;@shared;@split;@zp;atascii:;c64os:;cp437:;default:;iso16:;iso5:;iso:;kata:;petscii:;sc:" />
|
||||
<keywords3 keywords="^^bool;^^byte;^^float;^^long;^^str;^^ubyte;^^uword;^^word;bool;byte;const;float;long;str;struct;ubyte;uword;void;word" />
|
||||
<keywords4 keywords="abs;bmx;call;callfar;callfar2;cbm;clamp;cmp;conv;cx16;defer;diskio;divmod;floats;len;lsb;lsw;math;max;memory;min;mkword;msb;msw;peek;peekbool;peekf;peekw;poke;pokebool;pokef;pokew;psg;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sqrt;strings;sys;txt;verafx" />
|
||||
<keywords4 keywords="abs;bmx;call;callfar;callfar2;cbm;clamp;cmp;conv;cx16;defer;diskio;divmod;floats;len;lsb;lsw;math;max;memory;min;mkword;msb;msw;offsetof;peek;peekbool;peekf;peekw;poke;pokebool;pokef;pokew;psg;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sqrt;strings;sys;txt;verafx" />
|
||||
</highlighting>
|
||||
<extensionMap>
|
||||
<mapping ext="p8" />
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<Keywords name="Keywords1">void const
str
byte ubyte bool
long word uword
float
zp shared split nosplit requirezp nozp struct</Keywords>
|
||||
<Keywords name="Keywords2">%address
%asm
%ir
%asmbinary
%asminclude
%align
%breakpoint
%encoding
%import
%jmptable
%memtop
%launcher
%option
%output
%zeropage
%zpreserved
%zpallowed</Keywords>
|
||||
<Keywords name="Keywords3">inline sub asmsub extsub
clobbers
asm
if
when else
if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z
for in step do while repeat unroll
break continue return goto</Keywords>
|
||||
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sqrtw</Keywords>
|
||||
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof offsetof sqrtw</Keywords>
|
||||
<Keywords name="Keywords5">true false
not and or xor
as to downto |></Keywords>
|
||||
<Keywords name="Keywords6"></Keywords>
|
||||
<Keywords name="Keywords7"></Keywords>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<Keywords name="Keywords1">void const
str
byte ubyte bool
long word uword
float
zp shared split nosplit requirezp nozp struct</Keywords>
|
||||
<Keywords name="Keywords2">%address
%asm
%ir
%asmbinary
%asminclude
%align
%breakpoint
%encoding
%import
%jmptable
%memtop
%launcher
%option
%output
%zeropage
%zpreserved
%zpallowed</Keywords>
|
||||
<Keywords name="Keywords3">inline sub asmsub extsub
clobbers
asm
if
when else
if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z
for in step do while repeat unroll
break continue return goto</Keywords>
|
||||
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sqrtw</Keywords>
|
||||
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof offsetof sqrtw</Keywords>
|
||||
<Keywords name="Keywords5">true false
not and or xor
as to downto |></Keywords>
|
||||
<Keywords name="Keywords6"></Keywords>
|
||||
<Keywords name="Keywords7"></Keywords>
|
||||
|
||||
@@ -156,7 +156,7 @@ contexts:
|
||||
- match: (\b(const)\b)
|
||||
scope: storage.modifier.prog8
|
||||
support:
|
||||
- match: (\b(abs|atan|ceil|cos|cos8u|cos8|cos16u|cos16|deg|floor|ln|log2|rad|round|sin|sgn|sin8u|sin8|sin16u|sin16|sqrt16|sqrt|tan|any|all|len|max|min|reverse|sum|sort|memcopy|memset|memsetw|leftstr|rightstr|strlen|strcmp|strncmp|substr|exit|lsb|msb|lsw|msw|mkword|rnd|rndw|rndf|rol|rol2|ror|ror2|rsave|rrestore|read_flags|sizeof|set_carry|clear_carry|set_irqd|clear_irqd|swap)\b)
|
||||
- match: (\b(abs|atan|ceil|cos|cos8u|cos8|cos16u|cos16|deg|floor|ln|log2|rad|round|sin|sgn|sin8u|sin8|sin16u|sin16|sqrt16|sqrt|tan|any|all|len|max|min|reverse|sum|sort|memcopy|memset|memsetw|leftstr|rightstr|strlen|strcmp|strncmp|substr|exit|lsb|msb|lsw|msw|mkword|rnd|rndw|rndf|rol|rol2|ror|ror2|rsave|rrestore|read_flags|sizeof|offsetof|set_carry|clear_carry|set_irqd|clear_irqd|swap)\b)
|
||||
scope: support.function.prog8
|
||||
variable:
|
||||
- match: (\b\w+\b)
|
||||
|
||||
@@ -14,7 +14,7 @@ syn keyword prog8BuiltInFunc len
|
||||
|
||||
" Miscellaneous functions
|
||||
syn keyword prog8BuiltInFunc cmp divmod lsb msb lsw msw mkword min max peek peekw peekf peekbool poke pokew pokef pokebool rsave rsavex rrestore rrestorex
|
||||
syn keyword prog8BuiltInFunc rol rol2 ror ror2 sizeof setlsb setmsb
|
||||
syn keyword prog8BuiltInFunc rol rol2 ror ror2 sizeof offsetof setlsb setmsb
|
||||
syn keyword prog8BuiltInFunc memory call callfar callfar2 clamp defer alias
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user