mirror of
https://github.com/irmen/prog8.git
synced 2024-11-25 04:31:20 +00:00
Merge branch 'master' into next_compositetypes
# Conflicts: # codeCore/src/prog8/code/core/BuiltinFunctions.kt # codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt # codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt # codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt # compiler/src/prog8/compiler/astprocessing/AstChecker.kt # compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt # compiler/test/ast/TestAstChecks.kt
This commit is contained in:
commit
0f72bdb4a2
@ -1,23 +1,23 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="KotlinJavaRuntime" type="repository">
|
<library name="KotlinJavaRuntime" type="repository">
|
||||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20" />
|
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC>
|
<JAVADOC>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-javadoc.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-javadoc.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-javadoc.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-javadoc.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-javadoc.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-javadoc.jar!/" />
|
||||||
</JAVADOC>
|
</JAVADOC>
|
||||||
<SOURCES>
|
<SOURCES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-sources.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-sources.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-sources.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-sources.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-sources.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-sources.jar!/" />
|
||||||
</SOURCES>
|
</SOURCES>
|
||||||
</library>
|
</library>
|
||||||
</component>
|
</component>
|
@ -132,7 +132,6 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
|||||||
TODO("address-of array element $it in initial array value")
|
TODO("address-of array element $it in initial array value")
|
||||||
StArrayElement(null, it.identifier.name, null)
|
StArrayElement(null, it.identifier.name, null)
|
||||||
}
|
}
|
||||||
is PtIdentifier -> StArrayElement(null, it.name, null)
|
|
||||||
is PtNumber -> StArrayElement(it.number, null, null)
|
is PtNumber -> StArrayElement(it.number, null, null)
|
||||||
is PtBool -> StArrayElement(null, null, it.value)
|
is PtBool -> StArrayElement(null, null, it.value)
|
||||||
else -> throw AssemblyError("invalid array element $it")
|
else -> throw AssemblyError("invalid array element $it")
|
||||||
|
@ -161,6 +161,7 @@ class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(el
|
|||||||
|
|
||||||
|
|
||||||
class PtArray(type: DataType, position: Position): PtExpression(type, position) {
|
class PtArray(type: DataType, position: Position): PtExpression(type, position) {
|
||||||
|
// children are always one of 3 types: PtBool, PtNumber or PtAddressOf.
|
||||||
override fun hashCode(): Int = Objects.hash(children, type)
|
override fun hashCode(): Int = Objects.hash(children, type)
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if(other==null || other !is PtArray)
|
if(other==null || other !is PtArray)
|
||||||
|
@ -23,7 +23,17 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
|||||||
else
|
else
|
||||||
"&"
|
"&"
|
||||||
}
|
}
|
||||||
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
|
is PtArray -> {
|
||||||
|
val valuelist = node.children.map {
|
||||||
|
when (it) {
|
||||||
|
is PtBool -> it.toString()
|
||||||
|
is PtNumber -> it.number.toString()
|
||||||
|
is PtIdentifier -> it.name
|
||||||
|
else -> "?"
|
||||||
|
}
|
||||||
|
}.joinToString(", ")
|
||||||
|
"array len=${node.children.size} ${type(node.type)} [ $valuelist ]"
|
||||||
|
}
|
||||||
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
|
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
|
||||||
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
|
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
|
||||||
is PtBuiltinFunctionCall -> {
|
is PtBuiltinFunctionCall -> {
|
||||||
|
@ -87,7 +87,6 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
|||||||
// cmp returns a status in the carry flag, but not a proper return value
|
// cmp returns a status in the carry flag, but not a proper return value
|
||||||
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
||||||
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(BaseDataType.STR)), FParam("str2", arrayOf(BaseDataType.STR))), BaseDataType.BYTE),
|
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(BaseDataType.STR)), FParam("str2", arrayOf(BaseDataType.STR))), BaseDataType.BYTE),
|
||||||
"prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null),
|
|
||||||
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(BaseDataType.BYTE, BaseDataType.UBYTE))), BaseDataType.UBYTE),
|
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(BaseDataType.BYTE, BaseDataType.UBYTE))), BaseDataType.UBYTE),
|
||||||
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(BaseDataType.WORD, BaseDataType.UWORD))), BaseDataType.UWORD),
|
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(BaseDataType.WORD, BaseDataType.UWORD))), BaseDataType.UWORD),
|
||||||
"prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(BaseDataType.UBYTE))), BaseDataType.BOOL),
|
"prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(BaseDataType.UBYTE))), BaseDataType.BOOL),
|
||||||
|
@ -144,7 +144,6 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
|||||||
val newValue = PtArray(arrayValue.type, arrayValue.position)
|
val newValue = PtArray(arrayValue.type, arrayValue.position)
|
||||||
arrayValue.children.forEach { elt ->
|
arrayValue.children.forEach { elt ->
|
||||||
when(elt) {
|
when(elt) {
|
||||||
is PtIdentifier -> newValue.add(elt.prefix(arrayValue, st))
|
|
||||||
is PtBool -> newValue.add(elt)
|
is PtBool -> newValue.add(elt)
|
||||||
is PtNumber -> newValue.add(elt)
|
is PtNumber -> newValue.add(elt)
|
||||||
is PtAddressOf -> {
|
is PtAddressOf -> {
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package prog8.codegen.cpu6502
|
package prog8.codegen.cpu6502
|
||||||
|
|
||||||
import prog8.code.StMemVar
|
|
||||||
import prog8.code.StStaticVariable
|
|
||||||
import prog8.code.ast.*
|
import prog8.code.ast.*
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.codegen.cpu6502.assignment.*
|
import prog8.codegen.cpu6502.assignment.*
|
||||||
@ -71,101 +69,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
|||||||
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
|
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
|
||||||
"prog8_lib_square_byte" -> funcSquare(fcall, BaseDataType.UBYTE, resultRegister)
|
"prog8_lib_square_byte" -> funcSquare(fcall, BaseDataType.UBYTE, resultRegister)
|
||||||
"prog8_lib_square_word" -> funcSquare(fcall, BaseDataType.UWORD, resultRegister)
|
"prog8_lib_square_word" -> funcSquare(fcall, BaseDataType.UWORD, resultRegister)
|
||||||
"prog8_lib_arraycopy" -> funcArrayCopy(fcall)
|
|
||||||
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
|
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
return BuiltinFunctions.getValue(fcall.name).returnType
|
return BuiltinFunctions.getValue(fcall.name).returnType
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcArrayCopy(fcall: PtBuiltinFunctionCall) {
|
|
||||||
val source = fcall.args[0] as PtIdentifier
|
|
||||||
val target = fcall.args[1] as PtIdentifier
|
|
||||||
|
|
||||||
val numElements = when(val sourceSymbol = asmgen.symbolTable.lookup(source.name)) {
|
|
||||||
is StStaticVariable -> sourceSymbol.length!!
|
|
||||||
is StMemVar -> sourceSymbol.length!!
|
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
val sourceAsm = asmgen.asmVariableName(source)
|
|
||||||
val targetAsm = asmgen.asmVariableName(target)
|
|
||||||
|
|
||||||
if(source.type.isSplitWordArray && target.type.isSplitWordArray) {
|
|
||||||
// split -> split words (copy lsb and msb arrays separately)
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<${sourceAsm}_lsb
|
|
||||||
ldy #>${sourceAsm}_lsb
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
lda #<${targetAsm}_lsb
|
|
||||||
ldy #>${targetAsm}_lsb
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
ldy #${numElements and 255}
|
|
||||||
jsr prog8_lib.memcopy_small
|
|
||||||
lda #<${sourceAsm}_msb
|
|
||||||
ldy #>${sourceAsm}_msb
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
lda #<${targetAsm}_msb
|
|
||||||
ldy #>${targetAsm}_msb
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
ldy #${numElements and 255}
|
|
||||||
jsr prog8_lib.memcopy_small""")
|
|
||||||
}
|
|
||||||
else if(source.type.isSplitWordArray) {
|
|
||||||
// split word array to normal word array (copy lsb and msb arrays separately)
|
|
||||||
require(target.type.isWordArray && !target.type.isSplitWordArray)
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<${sourceAsm}_lsb
|
|
||||||
ldy #>${sourceAsm}_lsb
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
lda #<${sourceAsm}_msb
|
|
||||||
ldy #>${sourceAsm}_msb
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
lda #<${targetAsm}
|
|
||||||
ldy #>${targetAsm}
|
|
||||||
ldx #${numElements and 255}
|
|
||||||
jsr prog8_lib.arraycopy_split_to_normal_words""")
|
|
||||||
}
|
|
||||||
else if(target.type.isSplitWordArray) {
|
|
||||||
// normal word array to split array
|
|
||||||
require(source.type.isWordArray && !source.type.isSplitWordArray)
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<${targetAsm}_lsb
|
|
||||||
ldy #>${targetAsm}_lsb
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
lda #<${targetAsm}_msb
|
|
||||||
ldy #>${targetAsm}_msb
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
lda #<${sourceAsm}
|
|
||||||
ldy #>${sourceAsm}
|
|
||||||
ldx #${numElements and 255}
|
|
||||||
jsr prog8_lib.arraycopy_normal_to_split_words""")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// normal array to array copy, various element types
|
|
||||||
val eltsize = asmgen.options.compTarget.memorySize(source.type.sub!!)
|
|
||||||
val numBytes = numElements * eltsize
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<${sourceAsm}
|
|
||||||
ldy #>${sourceAsm}
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
lda #<${targetAsm}
|
|
||||||
ldy #>${targetAsm}
|
|
||||||
sta P8ZP_SCRATCH_W2
|
|
||||||
sty P8ZP_SCRATCH_W2+1
|
|
||||||
ldy #${numBytes and 255}
|
|
||||||
jsr prog8_lib.memcopy_small""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: BaseDataType, resultRegister: RegisterOrPair?) {
|
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: BaseDataType, resultRegister: RegisterOrPair?) {
|
||||||
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
|
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
|
||||||
when (resultType) {
|
when (resultType) {
|
||||||
@ -1357,22 +1266,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun outputAddressAndLengthOfArray(arg: PtIdentifier) {
|
|
||||||
// address goes in P8ZP_SCRATCH_W1, number of elements in A
|
|
||||||
val numElements = when(val symbol = asmgen.symbolTable.lookup(arg.name)) {
|
|
||||||
is StStaticVariable -> symbol.length!!
|
|
||||||
is StMemVar -> symbol.length!!
|
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
val identifierName = asmgen.asmVariableName(arg)
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$identifierName
|
|
||||||
ldy #>$identifierName
|
|
||||||
sta P8ZP_SCRATCH_W1
|
|
||||||
sty P8ZP_SCRATCH_W1+1
|
|
||||||
lda #${numElements and 255}""")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
|
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
|
||||||
val signature = BuiltinFunctions.getValue(call.name)
|
val signature = BuiltinFunctions.getValue(call.name)
|
||||||
val callConv = signature.callConvention(call.args.map {
|
val callConv = signature.callConvention(call.args.map {
|
||||||
|
@ -45,72 +45,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
|||||||
"prog8_lib_stringcompare" -> funcStringCompare(call)
|
"prog8_lib_stringcompare" -> funcStringCompare(call)
|
||||||
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
|
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
|
||||||
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
|
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
|
||||||
"prog8_lib_arraycopy" -> funcArrayCopy(call)
|
|
||||||
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcArrayCopy(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
|
||||||
val source = call.args[0] as PtIdentifier
|
|
||||||
val target = call.args[1] as PtIdentifier
|
|
||||||
val sourceLength = codeGen.symbolTable.getLength(source.name)!!
|
|
||||||
val targetLength = codeGen.symbolTable.getLength(target.name)!!
|
|
||||||
require(sourceLength==targetLength)
|
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
|
||||||
val fromReg = codeGen.registers.nextFree()
|
|
||||||
val toReg = codeGen.registers.nextFree()
|
|
||||||
val countReg = codeGen.registers.nextFree()
|
|
||||||
if(source.type.isSplitWordArray && target.type.isSplitWordArray) {
|
|
||||||
// split words -> split words, copy lsb and msb arrays separately
|
|
||||||
result += IRCodeChunk(null, null).also {
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb")
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb")
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength)
|
|
||||||
it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_msb")
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_msb")
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength)
|
|
||||||
it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(source.type.isSplitWordArray) {
|
|
||||||
// split -> normal words
|
|
||||||
require(target.type.isWordArray)
|
|
||||||
val fromRegMsb = codeGen.registers.nextFree()
|
|
||||||
result += IRCodeChunk(null, null).also {
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb")
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromRegMsb, labelSymbol = source.name+"_msb")
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name)
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength)
|
|
||||||
}
|
|
||||||
result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_SPLITW_TO_NORMAL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to fromRegMsb, IRDataType.WORD to toReg, IRDataType.BYTE to countReg), returns = null)
|
|
||||||
}
|
|
||||||
else if(target.type.isSplitWordArray) {
|
|
||||||
// normal -> split words
|
|
||||||
require(source.type.isWordArray)
|
|
||||||
val toRegMsb = codeGen.registers.nextFree()
|
|
||||||
result += IRCodeChunk(null, null).also {
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name)
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb")
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toRegMsb, labelSymbol = target.name+"_msb")
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength)
|
|
||||||
}
|
|
||||||
result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_NORMAL_TO_SPLITW, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.WORD to toRegMsb, IRDataType.BYTE to countReg), returns = null)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// normal array to array copy (various element types)
|
|
||||||
val eltsize = codeGen.options.compTarget.memorySize(source.type.sub!!)
|
|
||||||
result += IRCodeChunk(null, null).also {
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name)
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name)
|
|
||||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength * eltsize)
|
|
||||||
}
|
|
||||||
result += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
|
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
|
||||||
val result = mutableListOf<IRCodeChunkBase>()
|
val result = mutableListOf<IRCodeChunkBase>()
|
||||||
val valueTr = exprGen.translateExpression(call.args[0])
|
val valueTr = exprGen.translateExpression(call.args[0])
|
||||||
|
@ -2,6 +2,7 @@ package prog8.optimizer
|
|||||||
|
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.maySwapOperandOrder
|
import prog8.ast.maySwapOperandOrder
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
@ -113,6 +114,28 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(expr.left.inferType(program).isArray) {
|
||||||
|
if (expr.operator=="*" && rightconst!=null) {
|
||||||
|
if (expr.left is ArrayLiteral) {
|
||||||
|
// concatenate array literal.
|
||||||
|
val part = expr.left as ArrayLiteral
|
||||||
|
if(part.value.isEmpty())
|
||||||
|
errors.warn("resulting array has length zero", part.position)
|
||||||
|
val tmp = mutableListOf<Expression>()
|
||||||
|
repeat(rightconst.number.toInt()) {
|
||||||
|
part.value.forEach { tmp += it.copy() }
|
||||||
|
}
|
||||||
|
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl(program)
|
||||||
|
if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL)
|
||||||
|
throw FatalAstException("shouldn't see an array literal converted to an autovar here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(expr.operator=="==" && rightconst!=null) {
|
if(expr.operator=="==" && rightconst!=null) {
|
||||||
val leftExpr = expr.left as? BinaryExpression
|
val leftExpr = expr.left as? BinaryExpression
|
||||||
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
|
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
|
||||||
|
@ -269,7 +269,6 @@ class VarConstantValueTypeAdjuster(
|
|||||||
|
|
||||||
|
|
||||||
// Replace all constant identifiers with their actual value,
|
// Replace all constant identifiers with their actual value,
|
||||||
// and the array var initializer values and sizes.
|
|
||||||
// This is needed because further constant optimizations depend on those.
|
// This is needed because further constant optimizations depend on those.
|
||||||
internal class ConstantIdentifierReplacer(
|
internal class ConstantIdentifierReplacer(
|
||||||
private val program: Program,
|
private val program: Program,
|
||||||
@ -422,107 +421,43 @@ internal class ConstantIdentifierReplacer(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert the initializer range expression from a range or int, to an actual array.
|
val rangeExpr = decl.value as? RangeExpression ?: return null
|
||||||
// this is to allow initialization of arrays with a single value like ubyte[10] array = 42
|
|
||||||
|
// convert the initializer range expression from a range, to an actual array literal.
|
||||||
|
val declArraySize = decl.arraysize?.constIndex()
|
||||||
|
val constRange = rangeExpr.toConstantIntegerRange()
|
||||||
|
if(constRange?.isEmpty()==true) {
|
||||||
|
if(constRange.first>constRange.last && constRange.step>=0)
|
||||||
|
errors.err("descending range with positive step", decl.value?.position!!)
|
||||||
|
else if(constRange.first<constRange.last && constRange.step<=0)
|
||||||
|
errors.err("ascending range with negative step", decl.value?.position!!)
|
||||||
|
}
|
||||||
val dt = decl.datatype
|
val dt = decl.datatype
|
||||||
when {
|
when {
|
||||||
dt.isUnsignedByteArray || dt.isSignedByteArray || dt.isUnsignedWordArray || dt.isSignedWordArray -> {
|
dt.isUnsignedByteArray || dt.isSignedByteArray || dt.isUnsignedWordArray || dt.isSignedWordArray -> {
|
||||||
val rangeExpr = decl.value as? RangeExpression
|
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||||
if(rangeExpr!=null) {
|
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||||
val constRange = rangeExpr.toConstantIntegerRange()
|
if(constRange!=null) {
|
||||||
if(constRange?.isEmpty()==true) {
|
val rangeType = rangeExpr.inferType(program).getOr(DataType.forDt(BaseDataType.UBYTE))
|
||||||
if(constRange.first>constRange.last && constRange.step>=0)
|
return if(rangeType in ByteDatatypes) {
|
||||||
errors.err("descending range with positive step", decl.value?.position!!)
|
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||||
else if(constRange.first<constRange.last && constRange.step<=0)
|
constRange.map { NumericLiteral(rangeType.dt, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||||
errors.err("ascending range with negative step", decl.value?.position!!)
|
position = decl.value!!.position)
|
||||||
}
|
} else {
|
||||||
val declArraySize = decl.arraysize?.constIndex()
|
require(rangeType.sub!=null)
|
||||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
|
||||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
constRange.map { NumericLiteral(rangeType.dt, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||||
if(constRange!=null) {
|
|
||||||
val rangeType = rangeExpr.inferType(program).getOr(DataType.forDt(BaseDataType.UBYTE))
|
|
||||||
return if(rangeType.isByte) {
|
|
||||||
ArrayLiteral(InferredTypes.InferredType.known(dt),
|
|
||||||
constRange.map { NumericLiteral(rangeType.dt, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
|
||||||
position = decl.value!!.position)
|
|
||||||
} else {
|
|
||||||
require(rangeType.sub!=null)
|
|
||||||
ArrayLiteral(InferredTypes.InferredType.known(dt),
|
|
||||||
constRange.map { NumericLiteral(rangeType.sub!!.dt, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
|
||||||
position = decl.value!!.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val numericLv = decl.value as? NumericLiteral
|
|
||||||
if(numericLv!=null && numericLv.type == BaseDataType.FLOAT)
|
|
||||||
errors.err("arraysize requires only integers here", numericLv.position)
|
|
||||||
val size = decl.arraysize?.constIndex() ?: return null
|
|
||||||
if (rangeExpr==null && numericLv!=null) {
|
|
||||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
|
||||||
val fillvalue = numericLv.number.toInt()
|
|
||||||
when {
|
|
||||||
dt.isUnsignedByteArray -> {
|
|
||||||
if(fillvalue !in 0..255)
|
|
||||||
errors.err("ubyte value overflow", numericLv.position)
|
|
||||||
}
|
|
||||||
dt.isSignedByteArray -> {
|
|
||||||
if(fillvalue !in -128..127)
|
|
||||||
errors.err("byte value overflow", numericLv.position)
|
|
||||||
}
|
|
||||||
dt.isUnsignedWordArray -> {
|
|
||||||
if(fillvalue !in 0..65535)
|
|
||||||
errors.err("uword value overflow", numericLv.position)
|
|
||||||
}
|
|
||||||
dt.isSignedWordArray -> {
|
|
||||||
if(fillvalue !in -32768..32767)
|
|
||||||
errors.err("word value overflow", numericLv.position)
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
// create the array itself, filled with the fillvalue.
|
|
||||||
val array = Array(size) {fillvalue}.map { NumericLiteral(dt.elementType().dt, it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
|
||||||
return ArrayLiteral(InferredTypes.InferredType.known(dt), array, position = numericLv.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dt.isFloatArray -> {
|
|
||||||
val rangeExpr = decl.value as? RangeExpression
|
|
||||||
if(rangeExpr!=null) {
|
|
||||||
// convert the initializer range expression to an actual array of floats
|
|
||||||
val declArraySize = decl.arraysize?.constIndex()
|
|
||||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
|
||||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
|
||||||
val constRange = rangeExpr.toConstantIntegerRange()
|
|
||||||
if(constRange!=null) {
|
|
||||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.arrayFor(BaseDataType.FLOAT)),
|
|
||||||
constRange.map { NumericLiteral(BaseDataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
|
||||||
position = decl.value!!.position)
|
position = decl.value!!.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val numericLv = decl.value as? NumericLiteral
|
|
||||||
val size = decl.arraysize?.constIndex() ?: return null
|
|
||||||
if(rangeExpr==null && numericLv!=null) {
|
|
||||||
// arraysize initializer is a single int, and we know the array size.
|
|
||||||
val fillvalue = numericLv.number
|
|
||||||
if (fillvalue < options.compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > options.compTarget.machine.FLOAT_MAX_POSITIVE)
|
|
||||||
errors.err("float value overflow", numericLv.position)
|
|
||||||
else {
|
|
||||||
val array = Array(size) {fillvalue}.map { NumericLiteral(BaseDataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
|
||||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.arrayFor(BaseDataType.FLOAT)), array, position = numericLv.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dt.isBoolArray -> {
|
dt.isFloatArray -> {
|
||||||
val size = decl.arraysize?.constIndex() ?: return null
|
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||||
val numericLv = decl.value as? NumericLiteral
|
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||||
if(numericLv!=null) {
|
if(constRange!=null) {
|
||||||
// arraysize initializer is a single value, and we know the array size.
|
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||||
if(numericLv.type!=BaseDataType.BOOL) {
|
constRange.map { NumericLiteral(BaseDataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||||
errors.err("initializer value is not a boolean", numericLv.position)
|
position = decl.value!!.position)
|
||||||
return null
|
|
||||||
}
|
|
||||||
val array = Array(size) {numericLv.number}.map { NumericLiteral(BaseDataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()
|
|
||||||
return ArrayLiteral(InferredTypes.InferredType.known(DataType.arrayFor(BaseDataType.BOOL)), array, position = numericLv.position)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> return null
|
else -> return null
|
||||||
|
@ -717,12 +717,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(decl.isArray && decl.arraysize==null) {
|
if(decl.isArray && decl.arraysize==null) {
|
||||||
if(decl.type== VarDeclType.MEMORY)
|
if(decl.type== VarDeclType.MEMORY)
|
||||||
err("memory mapped array must have a size specification")
|
err("memory mapped array must have a size specification")
|
||||||
if(decl.value==null) {
|
if(decl.value==null || decl.value is NumericLiteral) {
|
||||||
valueerr("array variable is missing a size specification or an initialization value")
|
err("array variable is missing a size specification")
|
||||||
return
|
|
||||||
}
|
|
||||||
if(decl.value is NumericLiteral) {
|
|
||||||
valueerr("unsized array declaration cannot use a single literal initialization value")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(decl.value is RangeExpression)
|
if(decl.value is RangeExpression)
|
||||||
@ -804,6 +800,30 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
// array length limits and constant lenghts
|
// array length limits and constant lenghts
|
||||||
if(decl.isArray) {
|
if(decl.isArray) {
|
||||||
|
|
||||||
|
if(decl.type!=VarDeclType.MEMORY) {
|
||||||
|
// memory-mapped arrays are initialized with their address, but any other array needs a range or array literal value.
|
||||||
|
|
||||||
|
if (decl.value!=null && decl.value !is ArrayLiteral && decl.value !is RangeExpression) {
|
||||||
|
var suggestion: String? = null
|
||||||
|
val arraysize = decl.arraysize?.constIndex()
|
||||||
|
val numericvalue = decl.value?.constValue(program)
|
||||||
|
if (numericvalue != null && arraysize != null) {
|
||||||
|
when (numericvalue.type) {
|
||||||
|
in IntegerDatatypes -> suggestion = "[${numericvalue.number.toInt()}] * $arraysize"
|
||||||
|
DataType.FLOAT -> suggestion = "[${numericvalue.number}] * $arraysize"
|
||||||
|
DataType.BOOL -> suggestion = "[${numericvalue.asBooleanValue}] * $arraysize"
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suggestion != null)
|
||||||
|
valueerr("array initialization value must be a range value or an array literal (suggestion: use '$suggestion' here)")
|
||||||
|
else
|
||||||
|
valueerr("array initialization value must be a range value or an array literal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val length = decl.arraysize?.constIndex()
|
val length = decl.arraysize?.constIndex()
|
||||||
if(length==null)
|
if(length==null)
|
||||||
err("array length must be known at compile-time")
|
err("array length must be known at compile-time")
|
||||||
@ -992,16 +1012,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
checkValueTypeAndRangeArray(array.type.getOrUndef(), arrayspec, array)
|
checkValueTypeAndRangeArray(array.type.getOrUndef(), arrayspec, array)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPassByReferenceElement(e: Expression): Boolean {
|
|
||||||
if(e is IdentifierReference) {
|
|
||||||
val decl = e.targetVarDecl(program)
|
|
||||||
return decl?.datatype?.isPassByRef ?: true // is probably a symbol that needs addr-of
|
|
||||||
}
|
|
||||||
return e is StringLiteral
|
|
||||||
}
|
|
||||||
|
|
||||||
if(array.parent is VarDecl) {
|
if(array.parent is VarDecl) {
|
||||||
if (!array.value.all { it is NumericLiteral || it is AddressOf || isPassByReferenceElement(it) })
|
if (!array.value.all { it is NumericLiteral || it is AddressOf })
|
||||||
errors.err("array literal for variable initialization contains non-constant elements", array.position)
|
errors.err("array literal for variable initialization contains non-constant elements", array.position)
|
||||||
} else if(array.parent is ForLoop) {
|
} else if(array.parent is ForLoop) {
|
||||||
if (!array.value.all { it.constValue(program) != null })
|
if (!array.value.all { it.constValue(program) != null })
|
||||||
@ -1015,6 +1027,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(arraydt!=targetDt)
|
if(arraydt!=targetDt)
|
||||||
errors.err("value has incompatible type ($arraydt) for the variable ($targetDt)", array.position)
|
errors.err("value has incompatible type ($arraydt) for the variable ($targetDt)", array.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
super.visit(array)
|
super.visit(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1802,21 +1815,21 @@ internal class AstChecker(private val program: Program,
|
|||||||
sourceValue: Expression) : Boolean {
|
sourceValue: Expression) : Boolean {
|
||||||
val position = sourceValue.position
|
val position = sourceValue.position
|
||||||
|
|
||||||
if(sourceValue is ArrayLiteral && targetDatatype.isArray) {
|
if (targetDatatype.isArray) {
|
||||||
val vardecl=target.identifier?.targetVarDecl(program)
|
if(sourceValue.inferType(program).isArray)
|
||||||
val targetSize = vardecl?.arraysize?.constIndex()
|
errors.err("cannot assign arrays directly. Maybe use sys.memcopy instead.", target.position)
|
||||||
if(targetSize!=null) {
|
else
|
||||||
if(sourceValue.value.size != targetSize) {
|
errors.err("cannot assign value to array. Maybe use sys.memset/memsetw instead.", target.position)
|
||||||
errors.err("array size mismatch (expecting $targetSize, got ${sourceValue.value.size})", sourceValue.position)
|
return false
|
||||||
}
|
}
|
||||||
}
|
if (sourceValue is ArrayLiteral) {
|
||||||
|
errors.err("cannot assign array", target.position)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sourceValue is RangeExpression) {
|
if(sourceValue is RangeExpression) {
|
||||||
errors.err("can't assign a range value to something else", position)
|
errors.err("can't assign a range value to something else", position)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sourceDatatype.isUndefined) {
|
if(sourceDatatype.isUndefined) {
|
||||||
errors.err("assignment right hand side doesn't result in a value", position)
|
errors.err("assignment right hand side doesn't result in a value", position)
|
||||||
return false
|
return false
|
||||||
@ -1830,14 +1843,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
targetDatatype.isUnsignedWord -> sourceDatatype.isUnsignedWord || sourceDatatype.isUnsignedByte
|
targetDatatype.isUnsignedWord -> sourceDatatype.isUnsignedWord || sourceDatatype.isUnsignedByte
|
||||||
targetDatatype.isFloat -> sourceDatatype.isNumeric
|
targetDatatype.isFloat -> sourceDatatype.isNumeric
|
||||||
targetDatatype.isString -> sourceDatatype.isString
|
targetDatatype.isString -> sourceDatatype.isString
|
||||||
else -> {
|
else -> false
|
||||||
if(targetDatatype.isArray && sourceValue is ArrayLiteral)
|
|
||||||
true // assigning array literal to an array variable is allowed, size and type are checked elsewhere
|
|
||||||
else {
|
|
||||||
errors.err("cannot assign this value to variable of type $targetDatatype", position)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(result)
|
if(result)
|
||||||
|
@ -26,22 +26,6 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
|
|||||||
// - pointer[word] replaced by @(pointer+word)
|
// - pointer[word] replaced by @(pointer+word)
|
||||||
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
|
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
|
||||||
// - flatten chained assignments
|
// - flatten chained assignments
|
||||||
// - replace array assignments by a call to the builtin function that does this: prog8_lib_arraycopy
|
|
||||||
|
|
||||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
|
||||||
val targetArray = assignment.target.identifier?.targetVarDecl(program)
|
|
||||||
val sourceArray = (assignment.value as? IdentifierReference)?.targetVarDecl(program)
|
|
||||||
if(targetArray?.isArray==true && sourceArray?.isArray==true) {
|
|
||||||
val copy = FunctionCallStatement(
|
|
||||||
IdentifierReference(listOf("prog8_lib_arraycopy"), assignment.position),
|
|
||||||
mutableListOf(
|
|
||||||
IdentifierReference(sourceArray.scopedName, assignment.position),
|
|
||||||
IdentifierReference(targetArray.scopedName, assignment.position)
|
|
||||||
), false, assignment.position)
|
|
||||||
return listOf(IAstModification.ReplaceNode(assignment, copy, parent))
|
|
||||||
}
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||||
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
|
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
|
||||||
|
@ -567,8 +567,11 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
|||||||
|
|
||||||
private fun transform(srcArr: ArrayLiteral): PtArray {
|
private fun transform(srcArr: ArrayLiteral): PtArray {
|
||||||
val arr = PtArray(srcArr.inferType(program).getOrElse { throw FatalAstException("array must know its type") }, srcArr.position)
|
val arr = PtArray(srcArr.inferType(program).getOrElse { throw FatalAstException("array must know its type") }, srcArr.position)
|
||||||
for (elt in srcArr.value)
|
for (elt in srcArr.value) {
|
||||||
arr.add(transformExpression(elt))
|
val child = transformExpression(elt)
|
||||||
|
require(child is PtAddressOf || child is PtBool || child is PtNumber) { "array element invalid type $child" }
|
||||||
|
arr.add(child)
|
||||||
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val arrayDt = array.guessDatatype(program)
|
val arrayDt = array.guessDatatype(program)
|
||||||
|
if(arrayDt.isUnknown)
|
||||||
|
return noModifications
|
||||||
val elementDt = arrayDt.getOrUndef().elementType()
|
val elementDt = arrayDt.getOrUndef().elementType()
|
||||||
val maxSize = when {
|
val maxSize = when {
|
||||||
elementDt.isByteOrBool -> PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE
|
elementDt.isByteOrBool -> PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE
|
||||||
@ -60,17 +62,19 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
if(arrayDt.isKnown) {
|
if(arrayDt.isKnown) {
|
||||||
val parentAssign = parent as? Assignment
|
if((array.parent as? BinaryExpression)?.operator!="*") {
|
||||||
val targetDt = parentAssign?.target?.inferType(program) ?: arrayDt
|
val parentAssign = parent as? Assignment
|
||||||
// turn the array literal it into an identifier reference
|
val targetDt = parentAssign?.target?.inferType(program) ?: arrayDt
|
||||||
val litval2 = array.cast(targetDt.getOrUndef())
|
// turn the array literal it into an identifier reference
|
||||||
if(litval2!=null) {
|
val litval2 = array.cast(targetDt.getOrUndef())
|
||||||
val vardecl2 = VarDecl.createAuto(litval2, targetDt.getOrUndef().isSplitWordArray)
|
if (litval2 != null) {
|
||||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
val vardecl2 = VarDecl.createAuto(litval2, targetDt.getOrUndef().isSplitWordArray)
|
||||||
return listOf(
|
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||||
IAstModification.ReplaceNode(array, identifier, parent),
|
return listOf(
|
||||||
IAstModification.InsertFirst(vardecl2, array.definingScope)
|
IAstModification.ReplaceNode(array, identifier, parent),
|
||||||
)
|
IAstModification.InsertFirst(vardecl2, array.definingScope)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.FatalAstException
|
import prog8.ast.base.FatalAstException
|
||||||
@ -363,6 +364,25 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
|||||||
return adjustRangeDts(range, fromConst, fromDt, toConst, toDt, varDt.getOrUndef(), parent)
|
return adjustRangeDts(range, fromConst, fromDt, toConst, toDt, varDt.getOrUndef(), parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
|
||||||
|
// Arrays can contain booleans, numbers, or address-ofs.
|
||||||
|
// if there is an identifier here (that is of a pass-by-reference type), take its address explicitly.
|
||||||
|
|
||||||
|
for((index, elt) in array.value.withIndex()) {
|
||||||
|
if (elt is IdentifierReference) {
|
||||||
|
val eltType = elt.inferType(program)
|
||||||
|
val tgt = elt.targetStatement(program)
|
||||||
|
if(eltType.isPassByReference || tgt is Subroutine || tgt is Label || tgt is Block) {
|
||||||
|
val addressof = AddressOf(elt, null, elt.position)
|
||||||
|
addressof.linkParents(array)
|
||||||
|
array.value[index] = addressof
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
private fun adjustRangeDts(
|
private fun adjustRangeDts(
|
||||||
range: RangeExpression,
|
range: RangeExpression,
|
||||||
fromConst: NumericLiteral?,
|
fromConst: NumericLiteral?,
|
||||||
|
@ -116,6 +116,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
|||||||
"amiga",
|
"amiga",
|
||||||
"audioroutines",
|
"audioroutines",
|
||||||
"automatons",
|
"automatons",
|
||||||
|
"balloonflight",
|
||||||
"bdmusic",
|
"bdmusic",
|
||||||
"bobs",
|
"bobs",
|
||||||
"bubbleuniverse",
|
"bubbleuniverse",
|
||||||
|
@ -104,7 +104,7 @@ class TestAstChecks: FunSpec({
|
|||||||
val text = """
|
val text = """
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
const ubyte[5] a = 5
|
const ubyte[5] a = [1,2,3,4,5]
|
||||||
a[2]=42
|
a[2]=42
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,22 +133,6 @@ class TestAstChecks: FunSpec({
|
|||||||
errors.errors[0] shouldContain "indexing requires"
|
errors.errors[0] shouldContain "indexing requires"
|
||||||
}
|
}
|
||||||
|
|
||||||
test("array decl with expression as size can be initialized with a single value") {
|
|
||||||
val text = """
|
|
||||||
main {
|
|
||||||
sub start() {
|
|
||||||
const ubyte @shared n = 40
|
|
||||||
const ubyte @shared half = n / 2
|
|
||||||
ubyte[half] @shared a = 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
|
||||||
compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldNotBe null
|
|
||||||
errors.errors.size shouldBe 0
|
|
||||||
errors.warnings.size shouldBe 0
|
|
||||||
}
|
|
||||||
|
|
||||||
test("unicode in identifier names is working") {
|
test("unicode in identifier names is working") {
|
||||||
val text = """
|
val text = """
|
||||||
%import floats
|
%import floats
|
||||||
|
@ -4,7 +4,8 @@ import io.kotest.core.spec.style.FunSpec
|
|||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.kotest.matchers.shouldNotBe
|
import io.kotest.matchers.shouldNotBe
|
||||||
import io.kotest.matchers.string.shouldContain
|
import io.kotest.matchers.string.shouldContain
|
||||||
import prog8.code.ast.PtBuiltinFunctionCall
|
import io.kotest.matchers.types.instanceOf
|
||||||
|
import prog8.code.ast.*
|
||||||
import prog8.code.target.C64Target
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.VMTarget
|
import prog8.code.target.VMTarget
|
||||||
import prog8tests.helpers.ErrorReporterForTests
|
import prog8tests.helpers.ErrorReporterForTests
|
||||||
@ -152,25 +153,6 @@ main {
|
|||||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
||||||
}
|
}
|
||||||
|
|
||||||
test("split array assignments") {
|
|
||||||
val text = """
|
|
||||||
main {
|
|
||||||
sub start() {
|
|
||||||
str name1 = "name1"
|
|
||||||
str name2 = "name2"
|
|
||||||
uword[] @split names = [name1, name2, "name3"]
|
|
||||||
uword[] @split names2 = [name1, name2, "name3"]
|
|
||||||
uword[] addresses = [0,0,0]
|
|
||||||
names = [1111,2222,3333]
|
|
||||||
addresses = names
|
|
||||||
names = addresses
|
|
||||||
names2 = names
|
|
||||||
}
|
|
||||||
}"""
|
|
||||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
|
||||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
|
||||||
}
|
|
||||||
|
|
||||||
test("array target with expression for index") {
|
test("array target with expression for index") {
|
||||||
val text = """
|
val text = """
|
||||||
main {
|
main {
|
||||||
@ -329,79 +311,94 @@ main {
|
|||||||
errors.errors[2] shouldContain "out of bounds"
|
errors.errors[2] shouldContain "out of bounds"
|
||||||
}
|
}
|
||||||
|
|
||||||
test("array assignments should check for number of elements and element type correctness") {
|
test("array and string initializer with multiplication") {
|
||||||
val src="""
|
val src="""
|
||||||
%option enable_floats
|
%option enable_floats
|
||||||
|
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
ubyte[] array = 1 to 4
|
str name = "xyz" * 3
|
||||||
ubyte[] array2 = [1,2,3,4]
|
bool[3] boolarray = [true] * 3
|
||||||
str[] names = ["apple", "banana", "tomato"]
|
ubyte[3] bytearray = [42] * 3
|
||||||
|
uword[3] wordarray = [5555] * 3
|
||||||
|
float[3] floatarray = [123.45] * 3
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
|
||||||
|
val x = result.codegenAst!!.entrypoint()!!
|
||||||
|
x.children.size shouldBe 6
|
||||||
|
((x.children[0] as PtVariable).value as PtString).value shouldBe "xyzxyzxyz"
|
||||||
|
val array1 = (x.children[1] as PtVariable).value as PtArray
|
||||||
|
val array2 = (x.children[2] as PtVariable).value as PtArray
|
||||||
|
val array3 = (x.children[3] as PtVariable).value as PtArray
|
||||||
|
val array4 = (x.children[4] as PtVariable).value as PtArray
|
||||||
|
array1.children.map { (it as PtBool).value } shouldBe listOf(true, true, true)
|
||||||
|
array2.children.map { (it as PtNumber).number } shouldBe listOf(42, 42, 42)
|
||||||
|
array3.children.map { (it as PtNumber).number } shouldBe listOf(5555, 5555, 5555)
|
||||||
|
array4.children.map { (it as PtNumber).number } shouldBe listOf(123.45, 123.45, 123.45)
|
||||||
|
}
|
||||||
|
|
||||||
array = [10,11,12,13] ; ok!
|
test("array initializer with range") {
|
||||||
array = 20 to 23 ; ok!
|
val src="""
|
||||||
names = ["x1", "x2", "x3"] ; ok!
|
%option enable_floats
|
||||||
|
|
||||||
ubyte[] array3 = [1,2,3,4000] ; error: element type
|
main {
|
||||||
array = 10 to 15 ; error: array size
|
sub start() {
|
||||||
array = 1000 to 1003 ; error: element type
|
ubyte[3] bytearray2 = 10 to 12
|
||||||
names = ["x1", "x2", "x3", "x4"] ; error: array size
|
uword[3] wordarray2 = 5000 to 5002
|
||||||
names = [1.1, 2.2, 3.3, 4.4] ; error: array size AND element type
|
float[3] floatarray2 = 100 to 102
|
||||||
names = [1.1, 2.2, 999999.9] ; error: element type
|
}
|
||||||
names = [1.1, 2.2, 9.9] ; error: element type
|
}"""
|
||||||
|
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
|
||||||
|
val x = result.codegenAst!!.entrypoint()!!
|
||||||
|
x.children.size shouldBe 4
|
||||||
|
val array1 = (x.children[0] as PtVariable).value as PtArray
|
||||||
|
val array2 = (x.children[1] as PtVariable).value as PtArray
|
||||||
|
val array3 = (x.children[2] as PtVariable).value as PtArray
|
||||||
|
array1.children.map { (it as PtNumber).number } shouldBe listOf(10, 11, 12)
|
||||||
|
array2.children.map { (it as PtNumber).number } shouldBe listOf(5000, 5001, 5002)
|
||||||
|
array3.children.map { (it as PtNumber).number } shouldBe listOf(100, 101, 102)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("identifiers in array literals getting implicit address-of") {
|
||||||
|
val src="""
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
label:
|
||||||
|
str @shared name = "name"
|
||||||
|
uword[] @shared array1 = [name, label, start, main]
|
||||||
|
uword[] @shared array2 = [&name, &label, &start, &main]
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
|
||||||
|
val x = result.codegenAst!!.entrypoint()!!
|
||||||
|
x.children.size shouldBe 5
|
||||||
|
val array1 = (x.children[1] as PtVariable).value as PtArray
|
||||||
|
val array2 = (x.children[2] as PtVariable).value as PtArray
|
||||||
|
array1.children.forEach {
|
||||||
|
it shouldBe instanceOf<PtAddressOf>()
|
||||||
|
}
|
||||||
|
array2.children.forEach {
|
||||||
|
it shouldBe instanceOf<PtAddressOf>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("variable identifiers in array literals not getting implicit address-of") {
|
||||||
|
val src="""
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
label:
|
||||||
|
str @shared name = "name"
|
||||||
|
ubyte @shared bytevar
|
||||||
|
uword[] @shared array1 = [cx16.r0] ; error, is variables
|
||||||
|
uword[] @shared array2 = [bytevar] ; error, is variables
|
||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
val errors = ErrorReporterForTests()
|
val errors = ErrorReporterForTests()
|
||||||
compileText(C64Target(), false, src, writeAssembly = true, errors = errors) shouldBe null
|
compileText(C64Target(), false, src, writeAssembly = true, errors=errors) shouldBe null
|
||||||
errors.errors.size shouldBe 8
|
errors.errors.size shouldBe 2
|
||||||
errors.errors[0] shouldContain "incompatible type"
|
errors.errors[0] shouldContain "contains non-constant"
|
||||||
errors.errors[1] shouldContain "array size mismatch"
|
errors.errors[1] shouldContain "contains non-constant"
|
||||||
errors.errors[2] shouldContain "array element out of range"
|
|
||||||
errors.errors[3] shouldContain "array size mismatch"
|
|
||||||
errors.errors[4] shouldContain "array size mismatch"
|
|
||||||
errors.errors[5] shouldContain "value has incompatible type"
|
|
||||||
errors.errors[6] shouldContain "value has incompatible type"
|
|
||||||
errors.errors[7] shouldContain "value has incompatible type"
|
|
||||||
}
|
|
||||||
|
|
||||||
test("array assignments should work via array copy call") {
|
|
||||||
val src="""
|
|
||||||
%option enable_floats
|
|
||||||
|
|
||||||
main {
|
|
||||||
sub start() {
|
|
||||||
ubyte[] array = [1,2,3]
|
|
||||||
ubyte[3] array2
|
|
||||||
float[] flarray = [1.1, 2.2, 3.3]
|
|
||||||
float[3] flarray2
|
|
||||||
word[] warray = [-2222,42,3333]
|
|
||||||
word[3] warray2
|
|
||||||
str[] names = ["apple", "banana", "tomato"]
|
|
||||||
str[3] names2
|
|
||||||
|
|
||||||
; 8 array assignments -> 8 arraycopies:
|
|
||||||
array = [8,7,6]
|
|
||||||
array = array2
|
|
||||||
flarray = [99.9, 88.8, 77.7]
|
|
||||||
flarray = flarray2
|
|
||||||
warray = [4444,5555,6666]
|
|
||||||
warray = warray2
|
|
||||||
names = ["x1", "x2", "x3"]
|
|
||||||
names = names2
|
|
||||||
}
|
|
||||||
}"""
|
|
||||||
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
|
|
||||||
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
|
|
||||||
val x = result.codegenAst!!.entrypoint()!!
|
|
||||||
(x.children[12] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
|
|
||||||
(x.children[13] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
|
|
||||||
(x.children[14] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
|
|
||||||
(x.children[15] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
|
|
||||||
(x.children[16] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
|
|
||||||
(x.children[17] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
|
|
||||||
(x.children[18] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
|
|
||||||
(x.children[19] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import io.kotest.matchers.shouldBe
|
|||||||
import io.kotest.matchers.shouldNotBe
|
import io.kotest.matchers.shouldNotBe
|
||||||
import io.kotest.matchers.string.shouldContain
|
import io.kotest.matchers.string.shouldContain
|
||||||
import prog8.code.target.C64Target
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.VMTarget
|
|
||||||
import prog8tests.helpers.ErrorReporterForTests
|
import prog8tests.helpers.ErrorReporterForTests
|
||||||
import prog8tests.helpers.compileText
|
import prog8tests.helpers.compileText
|
||||||
|
|
||||||
@ -42,23 +41,6 @@ class TestVariables: FunSpec({
|
|||||||
compileText(C64Target(), true, text, writeAssembly = true) shouldNotBe null
|
compileText(C64Target(), true, text, writeAssembly = true) shouldNotBe null
|
||||||
}
|
}
|
||||||
|
|
||||||
test("array initialization with array var assignment") {
|
|
||||||
val text = """
|
|
||||||
main {
|
|
||||||
sub start() {
|
|
||||||
ubyte[3] @shared arrayvar=main.values1
|
|
||||||
arrayvar = main.values2
|
|
||||||
}
|
|
||||||
|
|
||||||
ubyte[] values1 = [1,2,3]
|
|
||||||
ubyte[] values2 = [1,2,3]
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
|
|
||||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
test("pipe character in string literal") {
|
test("pipe character in string literal") {
|
||||||
val text = """
|
val text = """
|
||||||
main {
|
main {
|
||||||
@ -113,32 +95,4 @@ class TestVariables: FunSpec({
|
|||||||
errors.errors[0] shouldContain "value has incompatible type"
|
errors.errors[0] shouldContain "value has incompatible type"
|
||||||
errors.errors[1] shouldContain "value has incompatible type"
|
errors.errors[1] shouldContain "value has incompatible type"
|
||||||
}
|
}
|
||||||
|
|
||||||
test("initialization of boolean array with single value") {
|
|
||||||
val text = """
|
|
||||||
main {
|
|
||||||
sub start() {
|
|
||||||
bool[10] sieve0 = false
|
|
||||||
bool[10] sieve1 = true
|
|
||||||
sieve0[0] = true
|
|
||||||
sieve1[0] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
|
|
||||||
}
|
|
||||||
|
|
||||||
test("initialization of boolean array with single value of wrong type fails") {
|
|
||||||
val text = """
|
|
||||||
main {
|
|
||||||
sub start() {
|
|
||||||
bool[10] sieve2 = 42
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
val errors = ErrorReporterForTests()
|
|
||||||
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
|
|
||||||
errors.errors.size shouldBe 1
|
|
||||||
errors.errors[0] shouldContain "initializer value is not a boolean"
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
@ -293,16 +293,16 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(array: ArrayLiteral) {
|
override fun visit(array: ArrayLiteral) {
|
||||||
outputListMembers(array.value.asSequence())
|
outputListMembers(array.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun outputListMembers(array: Sequence<Expression>) {
|
private fun outputListMembers(array: Array<Expression>) {
|
||||||
var counter = 0
|
var counter = 0
|
||||||
output("[")
|
output("[")
|
||||||
scopelevel++
|
scopelevel++
|
||||||
for (v in array) {
|
for ((idx, v) in array.withIndex()) {
|
||||||
v.accept(this)
|
v.accept(this)
|
||||||
if (v !== array.last())
|
if (idx != array.size-1)
|
||||||
output(", ")
|
output(", ")
|
||||||
counter++
|
counter++
|
||||||
if (counter > 16) {
|
if (counter > 16) {
|
||||||
|
@ -290,7 +290,7 @@ always have to be constants. Here are some examples of arrays::
|
|||||||
|
|
||||||
byte[10] array ; array of 10 bytes, initially set to 0
|
byte[10] array ; array of 10 bytes, initially set to 0
|
||||||
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
||||||
ubyte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
ubyte[99] array = [255]*99 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
||||||
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||||
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to array of uwords)
|
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to array of uwords)
|
||||||
uword[] others = [names, array] ; array of pointers/addresses to other arrays
|
uword[] others = [names, array] ; array of pointers/addresses to other arrays
|
||||||
@ -299,23 +299,25 @@ always have to be constants. Here are some examples of arrays::
|
|||||||
value = array[3] ; the fourth value in the array (index is 0-based)
|
value = array[3] ; the fourth value in the array (index is 0-based)
|
||||||
char = string[4] ; the fifth character (=byte) in the string
|
char = string[4] ; the fifth character (=byte) in the string
|
||||||
char = string[-2] ; the second-to-last character in the string (Python-style indexing from the end)
|
char = string[-2] ; the second-to-last character in the string (Python-style indexing from the end)
|
||||||
flags = [false, true] ; reset all flags in the array
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Right now, the array should be small enough to be indexable by a single byte index.
|
Right now, the array should be small enough to be indexable by a single byte index.
|
||||||
This means byte arrays should be <= 256 elements, word arrays <= 128 elements (256 if
|
This means byte arrays should be <= 256 elements, word arrays <= 128 elements (256 if
|
||||||
it's a split array - see below), and float arrays <= 51 elements.
|
it's a split array - see below), and float arrays <= 51 elements.
|
||||||
|
|
||||||
You can write out an array initializer list over several lines if you want to improve readability.
|
Arrays can be initialized with a range expression or an array literal value.
|
||||||
|
You can write out such an initializer value over several lines if you want to improve readability.
|
||||||
|
|
||||||
|
You can assign a new value to an element in the array, but you can't assign a whole
|
||||||
|
new array to another array at once. This is usually a costly operation. If you really
|
||||||
|
need this you have to write it out depending on the use case: you can copy the memory using
|
||||||
|
``sys.memcopy(sourcearray, targetarray, sizeof(targetarray))``. Or perhaps use ``sys.memset`` instead to
|
||||||
|
set it all to the same value, or maybe even simply assign the individual elements.
|
||||||
|
|
||||||
Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.)
|
Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.)
|
||||||
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
|
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
|
||||||
for instance.
|
for instance.
|
||||||
|
|
||||||
It is possible to assign an array (variable or array literal) to another array; this will overwrite all elements in the target
|
|
||||||
array with those in the source array. The number of elements in the arrays and the data types have to match.
|
|
||||||
For large arrays this is a slow operation because all values are copied over.
|
|
||||||
|
|
||||||
Using the ``in`` operator you can easily check if a value is present in an array,
|
Using the ``in`` operator you can easily check if a value is present in an array,
|
||||||
example: ``if choice in [1,2,3,4] {....}``
|
example: ``if choice in [1,2,3,4] {....}``
|
||||||
|
|
||||||
@ -377,8 +379,8 @@ You can concatenate two string literals using '+', which can be useful to
|
|||||||
split long strings over separate lines. But remember that the length
|
split long strings over separate lines. But remember that the length
|
||||||
of the total string still cannot exceed 255 characters.
|
of the total string still cannot exceed 255 characters.
|
||||||
A string literal can also be repeated a given number of times using '*', where the repeat number must be a constant value.
|
A string literal can also be repeated a given number of times using '*', where the repeat number must be a constant value.
|
||||||
And a new string value can be assigned to another string, but no bounds check is done
|
And a new string value can be assigned to another string, but no bounds check is done!
|
||||||
so be sure the destination string is large enough to contain the new value (it is overwritten in memory)::
|
So be sure the destination string is large enough to contain the new value (it is overwritten in memory)::
|
||||||
|
|
||||||
str string1 = "first part" + "second part"
|
str string1 = "first part" + "second part"
|
||||||
str string2 = "hello!" * 10
|
str string2 = "hello!" * 10
|
||||||
|
@ -363,7 +363,7 @@ Various examples::
|
|||||||
bool flag = true
|
bool flag = true
|
||||||
byte[] values = [11, 22, 33, 44, 55]
|
byte[] values = [11, 22, 33, 44, 55]
|
||||||
byte[5] values ; array of 5 bytes, initially set to zero
|
byte[5] values ; array of 5 bytes, initially set to zero
|
||||||
byte[5] values = 255 ; initialize with five 255 bytes
|
byte[5] values = [255]*5 ; initialize with five 255 bytes
|
||||||
|
|
||||||
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
||||||
uword @requirezp zpaddr = $3000 ; we require this variable in zeropage
|
uword @requirezp zpaddr = $3000 ; we require this variable in zeropage
|
||||||
@ -403,8 +403,11 @@ type identifier type storage size example var declara
|
|||||||
implicitly terminated by a 0-byte
|
implicitly terminated by a 0-byte
|
||||||
=============== ======================= ================= =========================================
|
=============== ======================= ================= =========================================
|
||||||
|
|
||||||
**arrays:** you can split an array initializer list over several lines if you want. When an initialization
|
**arrays:**
|
||||||
value is given, the array size in the declaration can be omitted.
|
Arrays can be initialized with a range expression or an array literal value.
|
||||||
|
You can write out such an initializer value over several lines if you want to improve readability.
|
||||||
|
When an initialization value is given, you are allowed to omit the array size in the declaration,
|
||||||
|
because it can be inferred from the initialization value.
|
||||||
|
|
||||||
**numbers:** unless prefixed for hex or binary as described below, all numbers are decimal numbers. There is no octal notation.
|
**numbers:** unless prefixed for hex or binary as described below, all numbers are decimal numbers. There is no octal notation.
|
||||||
|
|
||||||
@ -508,8 +511,6 @@ the downto variant to avoid having to specify the step as well::
|
|||||||
xx = 10
|
xx = 10
|
||||||
aa to xx ; range of 5, 6, 7, 8, 9, 10
|
aa to xx ; range of 5, 6, 7, 8, 9, 10
|
||||||
|
|
||||||
byte[] array = 10 to 13 ; sets the array to [10, 11, 12, 13]
|
|
||||||
|
|
||||||
for i in 0 to 127 {
|
for i in 0 to 127 {
|
||||||
; i loops 0, 1, 2, ... 127
|
; i loops 0, 1, 2, ... 127
|
||||||
}
|
}
|
||||||
@ -542,9 +543,10 @@ memory at the given index (and allows index values of word size). See :ref:`poin
|
|||||||
String
|
String
|
||||||
^^^^^^
|
^^^^^^
|
||||||
A string literal can occur with or without an encoding prefix (encoding followed by ':' followed by the string itself).
|
A string literal can occur with or without an encoding prefix (encoding followed by ':' followed by the string itself).
|
||||||
|
String length is limited to 255 characters.
|
||||||
|
You can use '+' and '*' to concatenate or repeat string fragments to build up a larger string literal.
|
||||||
When this is omitted, the string is stored in the machine's default character encoding (which is PETSCII on the CBM machines).
|
When this is omitted, the string is stored in the machine's default character encoding (which is PETSCII on the CBM machines).
|
||||||
You can choose to store the string in other encodings such as ``sc`` (screencodes) or ``iso`` (iso-8859-15).
|
You can choose to store the string in other encodings such as ``sc`` (screencodes) or ``iso`` (iso-8859-15).
|
||||||
String length is limited to 255 characters.
|
|
||||||
Here are examples of the various encodings:
|
Here are examples of the various encodings:
|
||||||
|
|
||||||
- ``"hello"`` a string translated into the default character encoding (PETSCII on the CBM machines)
|
- ``"hello"`` a string translated into the default character encoding (PETSCII on the CBM machines)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
TODO
|
TODO
|
||||||
====
|
====
|
||||||
|
|
||||||
|
- fixup syscall list UNUSED_SYSCALL_1 and 2 (numbers shift!)
|
||||||
|
|
||||||
Improve register load order in subroutine call args assignments:
|
Improve register load order in subroutine call args assignments:
|
||||||
in certain situations, the "wrong" order of evaluation of function call arguments is done which results
|
in certain situations, 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!)
|
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)
|
||||||
@ -9,11 +11,7 @@ Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEva
|
|||||||
|
|
||||||
Future Things and Ideas
|
Future Things and Ideas
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
- keep boolean array intact in IR so that it might be represented as a bitmask in the resulting code (8 times storage improvement)
|
||||||
- remove support for array variable initialization with a single value, just require explicitly creating the value array [42] * 10 (which is what the compiler now does for you implicitly)
|
|
||||||
- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does.
|
|
||||||
- should we add a cleararray builtin function that can efficiently set every element in the array to the given value
|
|
||||||
|
|
||||||
- improve detection that a variable is not read before being written so that initializing it to zero can be omitted (only happens now if a vardecl is immediately followed by a for loop for instance)
|
- improve detection that a variable is not read before being written so that initializing it to zero can be omitted (only happens now if a vardecl is immediately followed by a for loop for instance)
|
||||||
- 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
|
- 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
|
||||||
- Can we support signed % (remainder) somehow?
|
- Can we support signed % (remainder) somehow?
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
%import syslib
|
%import syslib
|
||||||
%import textio
|
%import textio
|
||||||
%import math
|
%import math
|
||||||
%zeropage basicsafe
|
|
||||||
|
|
||||||
|
; C64 version of a balloon sprites flying over a mountain landscape.
|
||||||
|
; There is also a X16 version of this in the examples.
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
|
||||||
@ -37,11 +38,16 @@ main {
|
|||||||
upwards = false
|
upwards = false
|
||||||
} else {
|
} else {
|
||||||
; determine new height for next mountain
|
; determine new height for next mountain
|
||||||
target_height = 9 + math.rnd() % 15
|
ubyte old_height = target_height
|
||||||
if upwards
|
if upwards {
|
||||||
mountain = 233
|
mountain = 233
|
||||||
else
|
while target_height >= old_height
|
||||||
|
target_height = 9 + math.rnd() % 15
|
||||||
|
} else {
|
||||||
mountain = 223
|
mountain = 223
|
||||||
|
while target_height <= old_height
|
||||||
|
target_height = 9 + math.rnd() % 15
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while not do_char_scroll {
|
while not do_char_scroll {
|
||||||
|
@ -12,9 +12,9 @@ main {
|
|||||||
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
|
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
|
||||||
|
|
||||||
; storage for rotated coordinates
|
; storage for rotated coordinates
|
||||||
float[len(xcoor)] rotatedx=0.0
|
float[len(xcoor)] rotatedx
|
||||||
float[len(ycoor)] rotatedy=0.0
|
float[len(ycoor)] rotatedy
|
||||||
float[len(zcoor)] rotatedz=-1.0
|
float[len(zcoor)] rotatedz
|
||||||
|
|
||||||
sub start() {
|
sub start() {
|
||||||
float time=0.0
|
float time=0.0
|
||||||
|
@ -25,7 +25,7 @@ main {
|
|||||||
for y in 32 to 199+32 {
|
for y in 32 to 199+32 {
|
||||||
cx16.FB_cursor_position((320-len(cells))/2,y)
|
cx16.FB_cursor_position((320-len(cells))/2,y)
|
||||||
cx16.FB_set_pixels(cells, len(cells))
|
cx16.FB_set_pixels(cells, len(cells))
|
||||||
cells_previous = cells
|
sys.memcopy(cells, cells_previous, sizeof(cells))
|
||||||
ubyte @zp x
|
ubyte @zp x
|
||||||
for x in 0 to len(cells)-1 {
|
for x in 0 to len(cells)-1 {
|
||||||
cells[x] = generate(x) ; next generation
|
cells[x] = generate(x) ; next generation
|
||||||
|
252
examples/cx16/balloonflight.p8
Normal file
252
examples/cx16/balloonflight.p8
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
%import syslib
|
||||||
|
%import textio
|
||||||
|
%import sprites
|
||||||
|
%import palette
|
||||||
|
%import math
|
||||||
|
|
||||||
|
; X16 version of a balloon sprites flying over a mountain landscape.
|
||||||
|
; There is also a C64 version of this in the examples.
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
ubyte target_height = txt.DEFAULT_HEIGHT - 10
|
||||||
|
ubyte active_height = txt.DEFAULT_HEIGHT
|
||||||
|
bool upwards = true
|
||||||
|
ubyte draw_column = txt.DEFAULT_WIDTH
|
||||||
|
word moon_x = 640
|
||||||
|
word balloon_y = 120
|
||||||
|
|
||||||
|
; clear the screen (including all the tiles outside of the visible area)
|
||||||
|
cx16.vaddr(txt.VERA_TEXTMATRIX_BANK, txt.VERA_TEXTMATRIX_ADDR, 0, 1)
|
||||||
|
repeat 128 * txt.DEFAULT_HEIGHT {
|
||||||
|
cx16.VERA_DATA0 = sc:' '
|
||||||
|
cx16.VERA_DATA0 = $00
|
||||||
|
}
|
||||||
|
|
||||||
|
; activate balloon and moon sprites
|
||||||
|
sprites.init(1, 0, $0000, sprites.SIZE_32, sprites.SIZE_64, sprites.COLORS_16, 1)
|
||||||
|
sprites.init(2, 0, $0400, sprites.SIZE_32, sprites.SIZE_32, sprites.COLORS_16, 2)
|
||||||
|
spritedata.copy_to_vram()
|
||||||
|
|
||||||
|
; Scroll!
|
||||||
|
; Unlike the C64 version, there is no need to copy the whole text matrix 1 character to the left
|
||||||
|
; every 8 pixels. The X16 has a much larger soft scroll register and the displayed tiles wrap around.
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
sys.waitvsync()
|
||||||
|
cx16.VERA_L1_HSCROLL ++
|
||||||
|
|
||||||
|
if cx16.VERA_L1_HSCROLL & 7 == 0 {
|
||||||
|
|
||||||
|
; set balloon pos
|
||||||
|
if math.rnd() & 1 != 0
|
||||||
|
balloon_y++
|
||||||
|
else
|
||||||
|
balloon_y--
|
||||||
|
sprites.pos(1, 100, balloon_y)
|
||||||
|
|
||||||
|
; set moon pos
|
||||||
|
moon_x--
|
||||||
|
if moon_x < -64
|
||||||
|
moon_x = 640
|
||||||
|
sprites.pos(2, moon_x, 20)
|
||||||
|
|
||||||
|
; update slope height
|
||||||
|
ubyte mountain = 223 ; slope upwards
|
||||||
|
if active_height < target_height {
|
||||||
|
active_height++
|
||||||
|
upwards = true
|
||||||
|
} else if active_height > target_height {
|
||||||
|
mountain = 233 ; slope downwards
|
||||||
|
active_height--
|
||||||
|
upwards = false
|
||||||
|
} else {
|
||||||
|
; determine new height for next mountain
|
||||||
|
ubyte old_height = target_height
|
||||||
|
if upwards {
|
||||||
|
mountain = 233
|
||||||
|
while target_height >= old_height
|
||||||
|
target_height = 28 + (math.rnd() & 31)
|
||||||
|
} else {
|
||||||
|
mountain = 223
|
||||||
|
while target_height <= old_height
|
||||||
|
target_height = 28 + (math.rnd() & 31)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
; draw new mountain etc.
|
||||||
|
draw_column++
|
||||||
|
ubyte yy
|
||||||
|
for yy in 0 to active_height-1 {
|
||||||
|
txt.setcc(draw_column, yy, 32, 2) ; clear top of screen
|
||||||
|
}
|
||||||
|
txt.setcc(draw_column, active_height, mountain, 8) ; mountain edge
|
||||||
|
for yy in active_height+1 to txt.DEFAULT_HEIGHT-1 {
|
||||||
|
txt.setcc(draw_column, yy, 160, 8) ; draw filled mountain
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte clutter = math.rnd()
|
||||||
|
if clutter > 100 {
|
||||||
|
; draw a star
|
||||||
|
txt.setcc(draw_column, clutter % (active_height-1), sc:'.', math.rnd())
|
||||||
|
}
|
||||||
|
|
||||||
|
if clutter > 200 {
|
||||||
|
; draw a tree
|
||||||
|
ubyte tree = sc:'↑'
|
||||||
|
ubyte treecolor = 5
|
||||||
|
if clutter & %00010000 != 0
|
||||||
|
tree = sc:'♣'
|
||||||
|
else if clutter & %00100000 != 0
|
||||||
|
tree = sc:'♠'
|
||||||
|
if math.rnd() > 130
|
||||||
|
treecolor = 13
|
||||||
|
txt.setcc(draw_column, active_height, tree, treecolor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if clutter > 235 {
|
||||||
|
; draw a camel
|
||||||
|
txt.setcc(draw_column, active_height, sc:'π', 9)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spritedata {
|
||||||
|
sub copy_to_vram() {
|
||||||
|
cx16.vaddr(0, $0000, 0, 1)
|
||||||
|
for cx16.r0 in 0 to 32*64/2-1
|
||||||
|
cx16.VERA_DATA0 = @(&balloonsprite + cx16.r0)
|
||||||
|
|
||||||
|
cx16.vaddr(0, $0400, 0, 1)
|
||||||
|
for cx16.r0 in 0 to 32*32/2-1
|
||||||
|
cx16.VERA_DATA0 = @(&moonsprite + cx16.r0)
|
||||||
|
|
||||||
|
for cx16.r1L in 0 to 15 {
|
||||||
|
palette.set_color(cx16.r1L + 16, balloon_pallette[cx16.r1L])
|
||||||
|
palette.set_color(cx16.r1L + 32, moon_pallette[cx16.r1L])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uword[] balloon_pallette = [
|
||||||
|
$f0f, $312, $603, $125,
|
||||||
|
$717, $721, $332, $a22,
|
||||||
|
$268, $d31, $764, $488,
|
||||||
|
$d71, $997, $ba7, $eb3
|
||||||
|
]
|
||||||
|
|
||||||
|
uword[] moon_pallette = [
|
||||||
|
$f0f, $444, $444, $555,
|
||||||
|
$555, $555, $555, $666,
|
||||||
|
$777, $777, $888, $888,
|
||||||
|
$999, $aaa, $bbb, $ccc
|
||||||
|
]
|
||||||
|
|
||||||
|
balloonsprite:
|
||||||
|
%asm {{
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $60, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $05, $99, $92, $55, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $05, $ac, $fc, $cf, $c9, $95, $e3, $25, $11, $40, $00, $00, $00
|
||||||
|
.byte $00, $00, $08, $3c, $ff, $cf, $ff, $c9, $9c, $c8, $37, $21, $33, $00, $00, $00
|
||||||
|
.byte $00, $00, $6b, $af, $ec, $ff, $ff, $9c, $9c, $fa, $33, $97, $23, $30, $00, $00
|
||||||
|
.byte $00, $08, $bd, $ef, $fc, $ff, $fc, $99, $99, $fc, $33, $27, $72, $33, $30, $00
|
||||||
|
.byte $00, $0b, $be, $ff, $cf, $ef, $fc, $9c, $99, $cf, $23, $32, $77, $13, $30, $00
|
||||||
|
.byte $00, $6b, $ee, $ff, $9f, $ef, $fc, $c9, $97, $cf, $d3, $32, $29, $73, $31, $00
|
||||||
|
.byte $00, $f8, $fe, $fc, $ff, $ef, $f9, $9c, $99, $ff, $d8, $31, $27, $72, $33, $00
|
||||||
|
.byte $3b, $fd, $ee, $fc, $ff, $ef, $f9, $9c, $99, $fe, $d3, $32, $24, $72, $33, $40
|
||||||
|
.byte $8b, $bd, $fe, $fc, $ff, $ef, $c7, $c7, $97, $ce, $d8, $33, $22, $72, $33, $30
|
||||||
|
.byte $b8, $bd, $ee, $d7, $db, $b8, $2d, $88, $32, $2b, $83, $33, $33, $33, $33, $10
|
||||||
|
.byte $18, $b8, $bb, $b8, $be, $bb, $3b, $b8, $38, $bb, $b8, $83, $33, $33, $33, $34
|
||||||
|
.byte $1b, $8b, $bb, $8b, $eb, $83, $3b, $b8, $33, $bb, $88, $33, $33, $33, $33, $33
|
||||||
|
.byte $8b, $b8, $8b, $88, $be, $88, $38, $88, $38, $8b, $b8, $33, $33, $33, $33, $33
|
||||||
|
.byte $8b, $88, $88, $8b, $b8, $83, $38, $88, $38, $8b, $88, $33, $33, $33, $33, $33
|
||||||
|
.byte $88, $83, $88, $88, $88, $83, $38, $83, $38, $8b, $83, $33, $33, $33, $33, $33
|
||||||
|
.byte $3b, $b8, $88, $88, $bb, $83, $38, $83, $38, $bb, $83, $33, $33, $33, $33, $33
|
||||||
|
.byte $6b, $bb, $88, $33, $bb, $b3, $38, $33, $33, $8b, $83, $33, $33, $33, $33, $30
|
||||||
|
.byte $1b, $b3, $73, $37, $78, $83, $27, $92, $42, $75, $83, $11, $33, $33, $33, $30
|
||||||
|
.byte $0c, $ff, $79, $97, $9f, $f9, $79, $92, $42, $79, $72, $12, $22, $11, $13, $10
|
||||||
|
.byte $04, $9c, $c9, $97, $9f, $fc, $79, $97, $44, $c7, $72, $12, $12, $12, $22, $00
|
||||||
|
.byte $00, $9c, $c9, $99, $59, $c9, $97, $92, $42, $7c, $21, $21, $22, $12, $21, $00
|
||||||
|
.byte $00, $59, $cc, $99, $59, $9c, $79, $97, $22, $75, $21, $41, $22, $14, $10, $00
|
||||||
|
.byte $00, $19, $cc, $99, $99, $9c, $97, $94, $22, $97, $21, $12, $11, $22, $10, $00
|
||||||
|
.byte $00, $01, $cc, $c9, $95, $99, $97, $77, $47, $92, $11, $22, $12, $21, $00, $00
|
||||||
|
.byte $00, $00, $79, $cc, $97, $7c, $c7, $99, $47, $72, $22, $11, $12, $14, $00, $00
|
||||||
|
.byte $00, $00, $07, $cc, $99, $7c, $fc, $99, $42, $91, $14, $21, $22, $00, $00, $00
|
||||||
|
.byte $00, $00, $01, $7c, $c9, $97, $cc, $79, $27, $72, $21, $12, $10, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $1a, $c5, $95, $fc, $77, $44, $22, $21, $21, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $04, $a7, $5c, $fe, $f7, $9c, $55, $52, $20, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $01, $57, $fe, $f9, $9c, $c5, $21, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $01, $5e, $c9, $95, $11, $10, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $12, $ff, $f2, $10, $00, $60, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $01, $ff, $f1, $10, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $01, $12, $11, $00, $0a, $40, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $06, $11, $11, $00, $06, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $15, $15, $10, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $5e, $ee, $ee, $e5, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $01, $7d, $de, $dd, $d2, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $05, $cc, $55, $55, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $05, $cc, $cc, $55, $51, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $05, $cc, $cc, $55, $55, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $05, $5c, $cc, $51, $51, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $05, $cc, $c5, $56, $54, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $05, $55, $55, $15, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||||
|
}}
|
||||||
|
|
||||||
|
moonsprite:
|
||||||
|
%asm {{
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $78, $88, $87, $50, $00, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $ba, $99, $9a, $a9, $88, $76, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $b9, $99, $77, $67, $79, $78, $89, $93, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $76, $69, $ca, $bc, $3b, $99, $78, $66, $99, $60, $00, $00, $00
|
||||||
|
.byte $00, $00, $04, $46, $cb, $64, $67, $77, $cb, $bb, $97, $99, $96, $00, $00, $00
|
||||||
|
.byte $00, $00, $62, $39, $93, $33, $36, $67, $ac, $cc, $79, $99, $98, $60, $00, $00
|
||||||
|
.byte $00, $06, $23, $47, $63, $43, $46, $79, $9b, $c6, $97, $96, $aa, $86, $00, $00
|
||||||
|
.byte $00, $06, $34, $36, $63, $43, $67, $37, $a9, $64, $46, $87, $ba, $98, $30, $00
|
||||||
|
.byte $00, $62, $46, $47, $76, $63, $66, $b7, $c9, $73, $46, $3a, $bc, $c9, $60, $00
|
||||||
|
.byte $00, $13, $39, $73, $33, $74, $36, $99, $b9, $34, $43, $67, $cb, $87, $44, $00
|
||||||
|
.byte $06, $43, $36, $37, $96, $76, $76, $3b, $d7, $34, $63, $48, $9a, $b9, $22, $00
|
||||||
|
.byte $03, $22, $63, $83, $79, $78, $99, $9a, $77, $77, $96, $33, $62, $9c, $22, $10
|
||||||
|
.byte $a3, $32, $36, $77, $bb, $ca, $a7, $63, $63, $97, $92, $33, $22, $9c, $22, $20
|
||||||
|
.byte $a6, $32, $37, $a7, $7b, $eb, $a7, $79, $b7, $7c, $93, $23, $22, $38, $86, $10
|
||||||
|
.byte $cb, $33, $37, $a7, $9b, $99, $33, $7a, $9d, $dc, $93, $33, $23, $36, $67, $10
|
||||||
|
.byte $cb, $23, $33, $77, $79, $99, $67, $76, $8e, $ee, $b7, $33, $63, $43, $22, $10
|
||||||
|
.byte $99, $33, $23, $36, $37, $39, $79, $ca, $bd, $dd, $dd, $a4, $a9, $62, $22, $20
|
||||||
|
.byte $b6, $72, $73, $37, $a7, $99, $7d, $ce, $de, $dd, $df, $97, $ca, $42, $22, $20
|
||||||
|
.byte $bb, $93, $33, $33, $b3, $6b, $37, $d9, $dd, $ed, $df, $c9, $bc, $92, $26, $30
|
||||||
|
.byte $0d, $d7, $39, $33, $73, $77, $77, $9b, $cd, $de, $ed, $ec, $96, $aa, $44, $10
|
||||||
|
.byte $0c, $cb, $9a, $b3, $39, $67, $76, $bc, $dd, $ee, $dd, $fb, $76, $77, $32, $00
|
||||||
|
.byte $0b, $dc, $cb, $33, $ca, $a7, $83, $dd, $cd, $dd, $dd, $dd, $a8, $a7, $a3, $00
|
||||||
|
.byte $00, $de, $dc, $73, $76, $73, $37, $ac, $ed, $ed, $dd, $ec, $bc, $b9, $63, $00
|
||||||
|
.byte $00, $bd, $ed, $b7, $b9, $c7, $99, $ee, $fd, $ec, $de, $ed, $cc, $cb, $70, $00
|
||||||
|
.byte $00, $0c, $ce, $d9, $c7, $9e, $ef, $ff, $ff, $ed, $ed, $db, $ca, $db, $30, $00
|
||||||
|
.byte $00, $0b, $9c, $c7, $cd, $de, $ef, $ff, $fe, $ed, $cc, $bc, $ba, $a7, $00, $00
|
||||||
|
.byte $00, $00, $bd, $9d, $dd, $de, $ff, $ff, $fe, $ee, $ec, $cc, $c9, $60, $00, $00
|
||||||
|
.byte $00, $00, $0b, $ca, $ac, $ee, $ff, $ff, $fe, $cc, $cd, $cd, $96, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $ba, $9d, $cc, $fe, $ff, $fd, $cc, $cc, $97, $60, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $9c, $da, $dd, $dc, $cd, $db, $a9, $83, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $08, $aa, $bd, $dc, $ac, $a9, $96, $00, $00, $00, $00, $00
|
||||||
|
.byte $00, $00, $00, $00, $00, $00, $59, $7b, $99, $60, $00, $00, $00, $00, $00, $00
|
||||||
|
}}
|
||||||
|
}
|
@ -1,42 +1,31 @@
|
|||||||
%import monogfx
|
%import floats
|
||||||
%import textio
|
%import textio
|
||||||
%import math
|
|
||||||
|
|
||||||
%option no_sysinit
|
%option no_sysinit
|
||||||
%zeropage basicsafe
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
|
||||||
sub start() {
|
sub start() {
|
||||||
monogfx.lores()
|
uword[4] words1 = [1,2,3,4]
|
||||||
demofill()
|
uword[4] words2 = [99,88,77,66]
|
||||||
}
|
|
||||||
|
|
||||||
sub demofill() {
|
for cx16.r0 in words1 {
|
||||||
const uword offsetx = 0
|
txt.print_uw(cx16.r0)
|
||||||
const uword offsety = 0
|
txt.spc()
|
||||||
|
}
|
||||||
monogfx.circle(offsetx+160, offsety+120, 110, true)
|
txt.nl()
|
||||||
monogfx.rect(offsetx+180, offsety+5, 25, 190, true)
|
cx16.r0L = words2
|
||||||
monogfx.line(offsetx+100, offsety+150, offsetx+240, offsety+10, true)
|
sys.memsetw(words1, len(words1), 99)
|
||||||
monogfx.line(offsetx+101, offsety+150, offsetx+241, offsety+10, true)
|
for cx16.r0 in words1 {
|
||||||
monogfx.rect(offsetx+150, offsety+130, 10, 100, true)
|
txt.print_uw(cx16.r0)
|
||||||
|
txt.spc()
|
||||||
sys.wait(30)
|
}
|
||||||
|
txt.nl()
|
||||||
cbm.SETTIM(0,0,0)
|
sys.memcopy([2222,3333,4444,5555], words1, sizeof(words1))
|
||||||
monogfx.fill(offsetx+100,offsety+100,true)
|
for cx16.r0 in words1 {
|
||||||
monogfx.fill(offsetx+100,offsety+100,false)
|
txt.print_uw(cx16.r0)
|
||||||
uword duration = cbm.RDTIM16()
|
txt.spc()
|
||||||
sys.wait(30)
|
}
|
||||||
|
|
||||||
monogfx.textmode()
|
|
||||||
txt.nl()
|
txt.nl()
|
||||||
txt.print_uw(duration)
|
|
||||||
txt.print(" jiffies\n")
|
|
||||||
|
|
||||||
; before optimizations: ~166 jiffies
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,5 @@ org.gradle.parallel=true
|
|||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
javaVersion=11
|
javaVersion=11
|
||||||
kotlinVersion=2.0.20
|
kotlinVersion=2.0.21
|
||||||
version=10.4.2
|
version=10.5-SNAPSHOT
|
||||||
|
@ -18,7 +18,5 @@ enum class IMSyscall(val number: Int) {
|
|||||||
CALLFAR(0x1017),
|
CALLFAR(0x1017),
|
||||||
CALLFAR2(0x1018),
|
CALLFAR2(0x1018),
|
||||||
MEMCOPY(0x1019),
|
MEMCOPY(0x1019),
|
||||||
MEMCOPY_SMALL(0x101a),
|
MEMCOPY_SMALL(0x101a)
|
||||||
ARRAYCOPY_SPLITW_TO_NORMAL(0x101b),
|
|
||||||
ARRAYCOPY_NORMAL_TO_SPLITW(0x101c),
|
|
||||||
}
|
}
|
||||||
|
298
scripts/cx16images.py
Normal file
298
scripts/cx16images.py
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
"""
|
||||||
|
Tools to convert bitmap images to an appropriate format for the Commander X16.
|
||||||
|
This means: indexed colors (palette), 12 bits color space (4 bits per channel, for a total of 4096 possible colors)
|
||||||
|
There are no restrictions on the size of the image.
|
||||||
|
|
||||||
|
Written by Irmen de Jong (irmen@razorvine.net) - Code is in the Public Domain.
|
||||||
|
|
||||||
|
Requirements: Pillow (pip install pillow)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from PIL import Image, PyAccess
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
RGBList: TypeAlias = list[tuple[int, int, int]]
|
||||||
|
|
||||||
|
# the 256 default colors of the Commander X16's color palette in (r,g,b) format
|
||||||
|
default_colors = []
|
||||||
|
|
||||||
|
_colors="""000,fff,800,afe,c4c,0c5,00a,ee7,d85,640,f77,333,777,af6,08f,bbb
|
||||||
|
000,111,222,333,444,555,666,777,888,999,aaa,bbb,ccc,ddd,eee,fff
|
||||||
|
211,433,644,866,a88,c99,fbb,211,422,633,844,a55,c66,f77,200,411
|
||||||
|
611,822,a22,c33,f33,200,400,600,800,a00,c00,f00,221,443,664,886
|
||||||
|
aa8,cc9,feb,211,432,653,874,a95,cb6,fd7,210,431,651,862,a82,ca3
|
||||||
|
fc3,210,430,640,860,a80,c90,fb0,121,343,564,786,9a8,bc9,dfb,121
|
||||||
|
342,463,684,8a5,9c6,bf7,120,241,461,582,6a2,8c3,9f3,120,240,360
|
||||||
|
480,5a0,6c0,7f0,121,343,465,686,8a8,9ca,bfc,121,242,364,485,5a6
|
||||||
|
6c8,7f9,020,141,162,283,2a4,3c5,3f6,020,041,061,082,0a2,0c3,0f3
|
||||||
|
122,344,466,688,8aa,9cc,bff,122,244,366,488,5aa,6cc,7ff,022,144
|
||||||
|
166,288,2aa,3cc,3ff,022,044,066,088,0aa,0cc,0ff,112,334,456,668
|
||||||
|
88a,9ac,bcf,112,224,346,458,56a,68c,79f,002,114,126,238,24a,35c
|
||||||
|
36f,002,014,016,028,02a,03c,03f,112,334,546,768,98a,b9c,dbf,112
|
||||||
|
324,436,648,85a,96c,b7f,102,214,416,528,62a,83c,93f,102,204,306
|
||||||
|
408,50a,60c,70f,212,434,646,868,a8a,c9c,fbe,211,423,635,847,a59
|
||||||
|
c6b,f7d,201,413,615,826,a28,c3a,f3c,201,403,604,806,a08,c09,f0b"""
|
||||||
|
|
||||||
|
for line in _colors.splitlines():
|
||||||
|
for rgb in line.split(","):
|
||||||
|
r = int(rgb[0], 16)
|
||||||
|
g = int(rgb[1], 16)
|
||||||
|
b = int(rgb[2], 16)
|
||||||
|
default_colors.append((r, g, b))
|
||||||
|
|
||||||
|
|
||||||
|
class BitmapImage:
|
||||||
|
def __init__(self, filename: str, image: Image = None) -> None:
|
||||||
|
"""Just load the given bitmap image file (any format allowed)."""
|
||||||
|
if image is not None:
|
||||||
|
self.img = image
|
||||||
|
else:
|
||||||
|
self.img = Image.open(filename)
|
||||||
|
self.size = self.img.size
|
||||||
|
self.width, self.height = self.size
|
||||||
|
|
||||||
|
def save(self, filename: str) -> None:
|
||||||
|
"""Save the image to a new file, format based on the file extension."""
|
||||||
|
self.img.save(filename)
|
||||||
|
|
||||||
|
def get_image(self) -> Image:
|
||||||
|
"""Gets access to a copy of the Pillow Image class that holds the loaded image"""
|
||||||
|
return self.img.copy()
|
||||||
|
|
||||||
|
def crop(self, x, y, width, height) -> "BitmapImage":
|
||||||
|
"""Returns a rectangle cropped from the original image"""
|
||||||
|
cropped = self.img.crop((x, y, x + width, y + height))
|
||||||
|
return BitmapImage("", cropped)
|
||||||
|
|
||||||
|
def has_palette(self) -> bool:
|
||||||
|
"""Is it an indexed colors image?"""
|
||||||
|
return self.img.mode == "P"
|
||||||
|
|
||||||
|
def get_palette(self) -> RGBList:
|
||||||
|
"""Return the image's palette as a list of (r,g,b) tuples"""
|
||||||
|
return flat_palette_to_rgb(self.img.getpalette())
|
||||||
|
|
||||||
|
def get_vera_palette(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns the image's palette as GB0R words (RGB in little-endian), suitable for the Vera palette registers.
|
||||||
|
The palette must be in 12 bit color space already! Because this routine just takes the upper 4 bits of every channel value.
|
||||||
|
"""
|
||||||
|
return rgb_palette_to_vera(self.get_palette())
|
||||||
|
|
||||||
|
def show(self) -> None:
|
||||||
|
"""Shows the image on the screen"""
|
||||||
|
if self.img.mode == "P":
|
||||||
|
self.img.convert("RGB").convert("P").show()
|
||||||
|
else:
|
||||||
|
self.img.show()
|
||||||
|
|
||||||
|
def get_pixels_8bpp(self, x: int, y: int, width: int, height: int) -> bytearray:
|
||||||
|
"""
|
||||||
|
For 8 bpp (256 color) images:
|
||||||
|
Get a rectangle of pixel values from the image, returns the bytes as a flat array
|
||||||
|
"""
|
||||||
|
assert self.has_palette()
|
||||||
|
try:
|
||||||
|
access = PyAccess.new(self.img, readonly=True)
|
||||||
|
except AttributeError:
|
||||||
|
access = self.img
|
||||||
|
data = bytearray(width * height)
|
||||||
|
index = 0
|
||||||
|
for py in range(y, y + height):
|
||||||
|
for px in range(x, x + width):
|
||||||
|
data[index] = access.getpixel((px, py))
|
||||||
|
index += 1
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_all_pixels_8bpp(self) -> bytes:
|
||||||
|
"""
|
||||||
|
For 8 bpp (256 color) images:
|
||||||
|
Get all pixel values from the image, returns the bytes as a flat array
|
||||||
|
"""
|
||||||
|
assert self.has_palette()
|
||||||
|
return self.img.tobytes()
|
||||||
|
# try:
|
||||||
|
# access = PyAccess.new(self.img, readonly=True)
|
||||||
|
# except AttributeError:
|
||||||
|
# access = self.img
|
||||||
|
# data = bytearray(self.width * self.height)
|
||||||
|
# index = 0
|
||||||
|
# for py in range(self.height):
|
||||||
|
# for px in range(self.width):
|
||||||
|
# data[index] = access.getpixel((px, py))
|
||||||
|
# index += 1
|
||||||
|
# return data
|
||||||
|
|
||||||
|
def get_pixels_4bpp(self, x: int, y: int, width: int, height: int) -> bytearray:
|
||||||
|
"""
|
||||||
|
For 4 bpp (16 color) images:
|
||||||
|
Get a rectangle of pixel values from the image, returns the bytes as a flat array.
|
||||||
|
Every byte encodes 2 pixels (4+4 bits).
|
||||||
|
"""
|
||||||
|
assert self.has_palette()
|
||||||
|
try:
|
||||||
|
access = PyAccess.new(self.img, readonly=True)
|
||||||
|
except AttributeError:
|
||||||
|
access = self.img
|
||||||
|
data = bytearray(width // 2 * height)
|
||||||
|
index = 0
|
||||||
|
for py in range(y, y + height):
|
||||||
|
for px in range(x, x + width, 2):
|
||||||
|
pix1 = access.getpixel((px, py))
|
||||||
|
pix2 = access.getpixel((px + 1, py))
|
||||||
|
data[index] = pix1 << 4 | pix2
|
||||||
|
index += 1
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_all_pixels_4bpp(self) -> bytearray:
|
||||||
|
"""
|
||||||
|
For 4 bpp (16 color) images:
|
||||||
|
Get all pixel values from the image, returns the bytes as a flat array.
|
||||||
|
Every byte encodes 2 pixels (4+4 bits).
|
||||||
|
"""
|
||||||
|
assert self.has_palette()
|
||||||
|
try:
|
||||||
|
access = PyAccess.new(self.img, readonly=True)
|
||||||
|
except AttributeError:
|
||||||
|
access = self.img
|
||||||
|
data = bytearray(self.width // 2 * self.height)
|
||||||
|
index = 0
|
||||||
|
for py in range(self.height):
|
||||||
|
for px in range(0, self.width, 2):
|
||||||
|
pix1 = access.getpixel((px, py))
|
||||||
|
pix2 = access.getpixel((px + 1, py))
|
||||||
|
data[index] = pix1 << 4 | pix2
|
||||||
|
index += 1
|
||||||
|
return data
|
||||||
|
|
||||||
|
def quantize_to(self, palette_rgb12: RGBList, dither: Image.Dither = Image.Dither.FLOYDSTEINBERG) -> None:
|
||||||
|
"""
|
||||||
|
Convert the image to one with the supplied palette.
|
||||||
|
This palette must be in 12 bits colorspace (4 bits so 0-15 per channel)
|
||||||
|
The resulting image will have its palette extended to 8 bits per channel again.
|
||||||
|
If you want to display the image on the actual Commander X16, simply take the lower (or upper) 4 bits of every color channel.
|
||||||
|
Dithering is applied as given (default is Floyd-Steinberg).
|
||||||
|
"""
|
||||||
|
palette_image = Image.new("P", (1, 1))
|
||||||
|
palette = []
|
||||||
|
for r, g, b in palette_rgb12:
|
||||||
|
palette.append(r << 4 | r)
|
||||||
|
palette.append(g << 4 | g)
|
||||||
|
palette.append(b << 4 | b)
|
||||||
|
palette_image.putpalette(palette)
|
||||||
|
self.img = self.img.quantize(dither=dither, palette=palette_image)
|
||||||
|
|
||||||
|
def quantize(self, bits_per_pixel: int, preserve_first_16_colors: bool,
|
||||||
|
dither: Image.Dither = Image.Dither.FLOYDSTEINBERG) -> None:
|
||||||
|
"""
|
||||||
|
Convert the image to one with indexed colors (12 bits colorspace palette extended back into 8 bits per channel).
|
||||||
|
If you want to display the image on the actual Commander X16, simply take the lower (or upper) 4 bits of every color channel.
|
||||||
|
There is support for either 8 or 4 bits per pixel (256 or 16 color modes).
|
||||||
|
Dithering is applied as given (default is Floyd-Steinberg).
|
||||||
|
"""
|
||||||
|
if bits_per_pixel == 8:
|
||||||
|
num_colors = 240 if preserve_first_16_colors else 256
|
||||||
|
elif bits_per_pixel == 4:
|
||||||
|
num_colors = 16
|
||||||
|
if preserve_first_16_colors:
|
||||||
|
return self.quantize_to(default_colors[:16])
|
||||||
|
elif bits_per_pixel == 2:
|
||||||
|
assert preserve_first_16_colors==False, "bpp is too small for 16 default colors"
|
||||||
|
num_colors = 4
|
||||||
|
elif bits_per_pixel == 1:
|
||||||
|
assert preserve_first_16_colors==False, "bpp is too small for 16 default colors"
|
||||||
|
num_colors = 2
|
||||||
|
else:
|
||||||
|
raise ValueError("only 8,4,2,1 bpp supported")
|
||||||
|
image = self.img.convert("RGB")
|
||||||
|
palette_image = image.quantize(colors=num_colors, dither=Image.Dither.NONE, method=Image.Quantize.MAXCOVERAGE)
|
||||||
|
if len(palette_image.getpalette()) // 3 > num_colors:
|
||||||
|
palette_image = image.quantize(colors=num_colors - 1, dither=Image.Dither.NONE, method=Image.Quantize.MAXCOVERAGE)
|
||||||
|
palette_rgb = flat_palette_to_rgb(palette_image.getpalette())
|
||||||
|
palette_rgb = list(reversed(sorted(set(palette_8to4(palette_rgb)))))
|
||||||
|
if preserve_first_16_colors:
|
||||||
|
palette_rgb = default_colors[:16] + palette_rgb
|
||||||
|
self.img = image
|
||||||
|
self.quantize_to(palette_rgb, dither)
|
||||||
|
|
||||||
|
def constrain_size(self, hires: bool = False) -> None:
|
||||||
|
"""
|
||||||
|
If the image is larger than the lores or hires screen size, scale it down so that it fits.
|
||||||
|
If the image already fits, doesn't do anything.
|
||||||
|
"""
|
||||||
|
w, h = self.img.size
|
||||||
|
if hires and (w > 640 or h > 480):
|
||||||
|
self.img.thumbnail((640, 480))
|
||||||
|
elif w > 320 or h > 240:
|
||||||
|
self.img.thumbnail((320, 240))
|
||||||
|
self.size = self.img.size
|
||||||
|
self.width, self.height = self.size
|
||||||
|
|
||||||
|
|
||||||
|
# utility functions
|
||||||
|
|
||||||
|
def channel_8to4(color: int) -> int:
|
||||||
|
"""Accurate conversion of a single 8 bit color channel value to 4 bits"""
|
||||||
|
return (color * 15 + 135) >> 8 # see https://threadlocalmutex.com/?p=48
|
||||||
|
|
||||||
|
|
||||||
|
def palette_8to4(palette_rgb: RGBList) -> RGBList:
|
||||||
|
"""Accurate conversion of a 24 bits palette (8 bits per channel) to a 12 bits palette (4 bits per channel)"""
|
||||||
|
converted = []
|
||||||
|
for ci in range(len(palette_rgb)):
|
||||||
|
r, g, b = palette_rgb[ci]
|
||||||
|
converted.append((channel_8to4(r), channel_8to4(g), channel_8to4(b)))
|
||||||
|
return converted
|
||||||
|
|
||||||
|
|
||||||
|
def reduce_colorspace(palette_rgb: RGBList) -> RGBList:
|
||||||
|
"""
|
||||||
|
Convert 24 bits color space (8 bits per channel) to 12 bits color space (4 bits per channel).
|
||||||
|
The resulting color values are still full 8 bits but their precision is reduced.
|
||||||
|
You can take either the upper or lower 4 bits of each channel byte to get the actual 4 bits precision.
|
||||||
|
"""
|
||||||
|
converted = []
|
||||||
|
for r, g, b in palette_rgb:
|
||||||
|
r = channel_8to4(r)
|
||||||
|
g = channel_8to4(g)
|
||||||
|
b = channel_8to4(b)
|
||||||
|
converted.append((r << 4 | r, g << 4 | g, b << 4 | b))
|
||||||
|
return converted
|
||||||
|
|
||||||
|
|
||||||
|
def flat_palette_to_rgb(palette: list[int]) -> RGBList:
|
||||||
|
"""Converts the flat palette list usually obtained from Pillow images to a list of (r,g,b) tuples"""
|
||||||
|
return [(palette[i], palette[i + 1], palette[i + 2]) for i in range(0, len(palette), 3)]
|
||||||
|
|
||||||
|
|
||||||
|
def rgb_palette_to_flat(palette: RGBList) -> list[int]:
|
||||||
|
"""Convert a palette of (r,g,b) tuples to a flat list that is usually used by Pillow images"""
|
||||||
|
result = []
|
||||||
|
for r, g, b in palette:
|
||||||
|
result.append(r)
|
||||||
|
result.append(g)
|
||||||
|
result.append(b)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def flat_palette_to_vera(palette: list[int]) -> bytearray:
|
||||||
|
"""
|
||||||
|
Convert a flat palette list usually obtained from Pillow images, to GB0R words (RGB in little-endian), suitable for Vera palette registers.
|
||||||
|
The palette must be in 12 bit color space already! Because this routine just takes the upper 4 bits of every channel value.
|
||||||
|
"""
|
||||||
|
return rgb_palette_to_vera(flat_palette_to_rgb(palette))
|
||||||
|
|
||||||
|
|
||||||
|
def rgb_palette_to_vera(palette_rgb: RGBList) -> bytearray:
|
||||||
|
"""
|
||||||
|
Convert a palette in (r,g,b) format to GB0R words (RGB in little-endian), suitable for Vera palette registers.
|
||||||
|
The palette must be in 12 bit color space already! Because this routine just takes the upper 4 bits of every channel value.
|
||||||
|
"""
|
||||||
|
data = bytearray()
|
||||||
|
for r, g, b in palette_rgb:
|
||||||
|
r = r >> 4
|
||||||
|
g = g >> 4
|
||||||
|
b = b >> 4
|
||||||
|
data.append(g << 4 | b)
|
||||||
|
data.append(r)
|
||||||
|
return data
|
@ -50,8 +50,8 @@ SYSCALLS:
|
|||||||
37 = memset
|
37 = memset
|
||||||
38 = memsetw
|
38 = memsetw
|
||||||
39 = stringcopy
|
39 = stringcopy
|
||||||
40 = ARRAYCOPY_SPLITW_TO_NORMAL
|
40 = ...unused...
|
||||||
41 = ARRAYCOPY_NORMAL_TO_SPLITW
|
41 = ...unused...
|
||||||
42 = memcopy_small
|
42 = memcopy_small
|
||||||
43 = load
|
43 = load
|
||||||
44 = load_raw
|
44 = load_raw
|
||||||
@ -103,8 +103,8 @@ enum class Syscall {
|
|||||||
MEMSET,
|
MEMSET,
|
||||||
MEMSETW,
|
MEMSETW,
|
||||||
STRINGCOPY,
|
STRINGCOPY,
|
||||||
ARRAYCOPY_SPLITW_TO_NORMAL,
|
UNUSED_SYSCALL_1, // TODO fixup
|
||||||
ARRAYCOPY_NORMAL_TO_SPLITW,
|
UNUSED_SYSCALL_2, // TODO fixup
|
||||||
MEMCOPY_SMALL,
|
MEMCOPY_SMALL,
|
||||||
LOAD,
|
LOAD,
|
||||||
LOAD_RAW,
|
LOAD_RAW,
|
||||||
@ -444,29 +444,6 @@ object SysCalls {
|
|||||||
vm.memory.setString(target, string, true)
|
vm.memory.setString(target, string, true)
|
||||||
returnValue(callspec.returns.single(), string.length, vm)
|
returnValue(callspec.returns.single(), string.length, vm)
|
||||||
}
|
}
|
||||||
Syscall.ARRAYCOPY_SPLITW_TO_NORMAL -> {
|
|
||||||
val (fromLsbA, fromMsbA, targetA, bytecountA) = getArgValues(callspec.arguments, vm)
|
|
||||||
val fromLsb = (fromLsbA as UShort).toInt()
|
|
||||||
val fromMsb = (fromMsbA as UShort).toInt()
|
|
||||||
val target = (targetA as UShort).toInt()
|
|
||||||
val bytecount = (bytecountA as UByte).toInt()
|
|
||||||
for(offset in 0..<bytecount) {
|
|
||||||
vm.memory.setUB(target+offset*2, vm.memory.getUB(fromLsb+offset))
|
|
||||||
vm.memory.setUB(target+offset*2+1, vm.memory.getUB(fromMsb+offset))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Syscall.ARRAYCOPY_NORMAL_TO_SPLITW -> {
|
|
||||||
val (fromA, targetLsbA, targetMsbA, bytecountA) = getArgValues(callspec.arguments, vm)
|
|
||||||
val from = (fromA as UShort).toInt()
|
|
||||||
val targetLsb = (targetLsbA as UShort).toInt()
|
|
||||||
val targetMsb = (targetMsbA as UShort).toInt()
|
|
||||||
val bytecount = (bytecountA as UByte).toInt()
|
|
||||||
for(offset in 0..<bytecount) {
|
|
||||||
vm.memory.setUB(targetLsb+offset, vm.memory.getUB(from+offset*2))
|
|
||||||
vm.memory.setUB(targetMsb+offset, vm.memory.getUB(from+offset*2+1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Syscall.LOAD -> {
|
Syscall.LOAD -> {
|
||||||
val (filenameA, addrA) = getArgValues(callspec.arguments, vm)
|
val (filenameA, addrA) = getArgValues(callspec.arguments, vm)
|
||||||
val filename = vm.memory.getString((filenameA as UShort).toInt())
|
val filename = vm.memory.getString((filenameA as UShort).toInt())
|
||||||
@ -572,6 +549,9 @@ object SysCalls {
|
|||||||
}
|
}
|
||||||
return returnValue(callspec.returns.single(), 30*256 + 80, vm) // just return some defaults in this case 80*30
|
return returnValue(callspec.returns.single(), 30*256 + 80, vm) // just return some defaults in this case 80*30
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Syscall.UNUSED_SYSCALL_1 -> TODO("remove this")
|
||||||
|
Syscall.UNUSED_SYSCALL_2 -> TODO("remove this")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,8 +115,6 @@ class VmProgramLoader {
|
|||||||
IMSyscall.CALLFAR2.number -> throw IRParseException("vm doesn't support the callfar2() syscall")
|
IMSyscall.CALLFAR2.number -> throw IRParseException("vm doesn't support the callfar2() syscall")
|
||||||
IMSyscall.MEMCOPY.number -> Syscall.MEMCOPY
|
IMSyscall.MEMCOPY.number -> Syscall.MEMCOPY
|
||||||
IMSyscall.MEMCOPY_SMALL.number -> Syscall.MEMCOPY_SMALL
|
IMSyscall.MEMCOPY_SMALL.number -> Syscall.MEMCOPY_SMALL
|
||||||
IMSyscall.ARRAYCOPY_SPLITW_TO_NORMAL.number -> Syscall.ARRAYCOPY_SPLITW_TO_NORMAL
|
|
||||||
IMSyscall.ARRAYCOPY_NORMAL_TO_SPLITW.number -> Syscall.ARRAYCOPY_NORMAL_TO_SPLITW
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user