diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml
index 98e799522..cf8a55908 100644
--- a/.idea/libraries/KotlinJavaRuntime.xml
+++ b/.idea/libraries/KotlinJavaRuntime.xml
@@ -1,23 +1,23 @@
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
\ No newline at end of file
diff --git a/codeCore/src/prog8/code/SymbolTableMaker.kt b/codeCore/src/prog8/code/SymbolTableMaker.kt
index 997e21b39..e370e43e4 100644
--- a/codeCore/src/prog8/code/SymbolTableMaker.kt
+++ b/codeCore/src/prog8/code/SymbolTableMaker.kt
@@ -132,7 +132,6 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
TODO("address-of array element $it in initial array value")
StArrayElement(null, it.identifier.name, null)
}
- is PtIdentifier -> StArrayElement(null, it.name, null)
is PtNumber -> StArrayElement(it.number, null, null)
is PtBool -> StArrayElement(null, null, it.value)
else -> throw AssemblyError("invalid array element $it")
diff --git a/codeCore/src/prog8/code/ast/AstExpressions.kt b/codeCore/src/prog8/code/ast/AstExpressions.kt
index 7df9b862b..59466e1de 100644
--- a/codeCore/src/prog8/code/ast/AstExpressions.kt
+++ b/codeCore/src/prog8/code/ast/AstExpressions.kt
@@ -161,6 +161,7 @@ class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(el
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 equals(other: Any?): Boolean {
if(other==null || other !is PtArray)
diff --git a/codeCore/src/prog8/code/ast/AstPrinter.kt b/codeCore/src/prog8/code/ast/AstPrinter.kt
index 41ca0c99b..271e94564 100644
--- a/codeCore/src/prog8/code/ast/AstPrinter.kt
+++ b/codeCore/src/prog8/code/ast/AstPrinter.kt
@@ -23,7 +23,17 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
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 -> " ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
is PtBinaryExpression -> " ${node.operator} ${type(node.type)}"
is PtBuiltinFunctionCall -> {
diff --git a/codeCore/src/prog8/code/core/BuiltinFunctions.kt b/codeCore/src/prog8/code/core/BuiltinFunctions.kt
index 96221f6af..4847c7d95 100644
--- a/codeCore/src/prog8/code/core/BuiltinFunctions.kt
+++ b/codeCore/src/prog8/code/core/BuiltinFunctions.kt
@@ -87,7 +87,6 @@ val BuiltinFunctions: Map = mapOf(
// 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),
"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_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),
diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
index c4f9e319e..455346da2 100644
--- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
+++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
@@ -144,7 +144,6 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
val newValue = PtArray(arrayValue.type, arrayValue.position)
arrayValue.children.forEach { elt ->
when(elt) {
- is PtIdentifier -> newValue.add(elt.prefix(arrayValue, st))
is PtBool -> newValue.add(elt)
is PtNumber -> newValue.add(elt)
is PtAddressOf -> {
diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt
index ecd0d6855..1bbf61839 100644
--- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt
+++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt
@@ -1,7 +1,5 @@
package prog8.codegen.cpu6502
-import prog8.code.StMemVar
-import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
@@ -71,101 +69,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, BaseDataType.UBYTE, 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}")
}
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?) {
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
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?) {
val signature = BuiltinFunctions.getValue(call.name)
val callConv = signature.callConvention(call.args.map {
diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt
index 842dc0a5d..35bab7218 100644
--- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt
+++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt
@@ -45,72 +45,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
- "prog8_lib_arraycopy" -> funcArrayCopy(call)
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()
- 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 {
val result = mutableListOf()
val valueTr = exprGen.translateExpression(call.args[0])
diff --git a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt
index 929225198..aa68d0ad0 100644
--- a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt
+++ b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt
@@ -2,6 +2,7 @@ package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
+import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.maySwapOperandOrder
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()
+ 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) {
val leftExpr = expr.left as? BinaryExpression
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)
diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt
index 7afa79acb..c12fb258c 100644
--- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt
+++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt
@@ -269,7 +269,6 @@ class VarConstantValueTypeAdjuster(
// 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.
internal class ConstantIdentifierReplacer(
private val program: Program,
@@ -422,107 +421,43 @@ internal class ConstantIdentifierReplacer(
return null
}
- // convert the initializer range expression from a range or int, to an actual array.
- // this is to allow initialization of arrays with a single value like ubyte[10] array = 42
+ val rangeExpr = decl.value as? RangeExpression ?: return null
+
+ // 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 {
- val rangeExpr = decl.value as? RangeExpression
- if(rangeExpr!=null) {
- 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 {
- 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()
- 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(),
+ if(declArraySize!=null && declArraySize!=rangeExpr.size())
+ errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
+ if(constRange!=null) {
+ val rangeType = rangeExpr.inferType(program).getOr(DataType.forDt(BaseDataType.UBYTE))
+ return if(rangeType in ByteDatatypes) {
+ ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
+ constRange.map { NumericLiteral(rangeType.dt, it.toDouble(), decl.value!!.position) }.toTypedArray(),
+ position = decl.value!!.position)
+ } else {
+ require(rangeType.sub!=null)
+ ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
+ constRange.map { NumericLiteral(rangeType.dt, it.toDouble(), decl.value!!.position) }.toTypedArray(),
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()
- return ArrayLiteral(InferredTypes.InferredType.known(DataType.arrayFor(BaseDataType.FLOAT)), array, position = numericLv.position)
- }
- }
}
- dt.isBoolArray -> {
- val size = decl.arraysize?.constIndex() ?: return null
- val numericLv = decl.value as? NumericLiteral
- if(numericLv!=null) {
- // arraysize initializer is a single value, and we know the array size.
- if(numericLv.type!=BaseDataType.BOOL) {
- errors.err("initializer value is not a boolean", numericLv.position)
- return null
- }
- val array = Array(size) {numericLv.number}.map { NumericLiteral(BaseDataType.BOOL, it, numericLv.position) }.toTypedArray()
- return ArrayLiteral(InferredTypes.InferredType.known(DataType.arrayFor(BaseDataType.BOOL)), array, position = numericLv.position)
+ dt.isFloatArray -> {
+ if(declArraySize!=null && declArraySize!=rangeExpr.size())
+ errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
+ if(constRange!=null) {
+ return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
+ constRange.map { NumericLiteral(BaseDataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
+ position = decl.value!!.position)
}
}
else -> return null
diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt
index ccb66ed40..62a49e3c7 100644
--- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt
+++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt
@@ -717,12 +717,8 @@ internal class AstChecker(private val program: Program,
if(decl.isArray && decl.arraysize==null) {
if(decl.type== VarDeclType.MEMORY)
err("memory mapped array must have a size specification")
- if(decl.value==null) {
- valueerr("array variable is missing a size specification or an initialization value")
- return
- }
- if(decl.value is NumericLiteral) {
- valueerr("unsized array declaration cannot use a single literal initialization value")
+ if(decl.value==null || decl.value is NumericLiteral) {
+ err("array variable is missing a size specification")
return
}
if(decl.value is RangeExpression)
@@ -804,6 +800,30 @@ internal class AstChecker(private val program: Program,
// array length limits and constant lenghts
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()
if(length==null)
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)
}
- 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.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)
} else if(array.parent is ForLoop) {
if (!array.value.all { it.constValue(program) != null })
@@ -1015,6 +1027,7 @@ internal class AstChecker(private val program: Program,
if(arraydt!=targetDt)
errors.err("value has incompatible type ($arraydt) for the variable ($targetDt)", array.position)
}
+
super.visit(array)
}
@@ -1802,21 +1815,21 @@ internal class AstChecker(private val program: Program,
sourceValue: Expression) : Boolean {
val position = sourceValue.position
- if(sourceValue is ArrayLiteral && targetDatatype.isArray) {
- val vardecl=target.identifier?.targetVarDecl(program)
- val targetSize = vardecl?.arraysize?.constIndex()
- if(targetSize!=null) {
- if(sourceValue.value.size != targetSize) {
- errors.err("array size mismatch (expecting $targetSize, got ${sourceValue.value.size})", sourceValue.position)
- }
- }
+ if (targetDatatype.isArray) {
+ if(sourceValue.inferType(program).isArray)
+ errors.err("cannot assign arrays directly. Maybe use sys.memcopy instead.", target.position)
+ else
+ errors.err("cannot assign value to array. Maybe use sys.memset/memsetw instead.", target.position)
+ return false
+ }
+ if (sourceValue is ArrayLiteral) {
+ errors.err("cannot assign array", target.position)
+ return false
}
-
if(sourceValue is RangeExpression) {
errors.err("can't assign a range value to something else", position)
return false
}
-
if(sourceDatatype.isUndefined) {
errors.err("assignment right hand side doesn't result in a value", position)
return false
@@ -1830,14 +1843,7 @@ internal class AstChecker(private val program: Program,
targetDatatype.isUnsignedWord -> sourceDatatype.isUnsignedWord || sourceDatatype.isUnsignedByte
targetDatatype.isFloat -> sourceDatatype.isNumeric
targetDatatype.isString -> sourceDatatype.isString
- else -> {
- 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
- }
- }
+ else -> false
}
if(result)
diff --git a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
index 06e8b6083..7b92223ca 100644
--- a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
+++ b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
@@ -26,22 +26,6 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// - pointer[word] replaced by @(pointer+word)
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
// - 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 {
- 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 {
fun jumpAfter(stmt: Statement): Iterable {
diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt
index 09489012b..3b0c6f7af 100644
--- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt
+++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt
@@ -567,8 +567,11 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transform(srcArr: ArrayLiteral): PtArray {
val arr = PtArray(srcArr.inferType(program).getOrElse { throw FatalAstException("array must know its type") }, srcArr.position)
- for (elt in srcArr.value)
- arr.add(transformExpression(elt))
+ for (elt in srcArr.value) {
+ val child = transformExpression(elt)
+ require(child is PtAddressOf || child is PtBool || child is PtNumber) { "array element invalid type $child" }
+ arr.add(child)
+ }
return arr
}
diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt
index f7acb92e9..9d03eb116 100644
--- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt
+++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt
@@ -49,6 +49,8 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
}
} else {
val arrayDt = array.guessDatatype(program)
+ if(arrayDt.isUnknown)
+ return noModifications
val elementDt = arrayDt.getOrUndef().elementType()
val maxSize = when {
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
}
if(arrayDt.isKnown) {
- val parentAssign = parent as? Assignment
- val targetDt = parentAssign?.target?.inferType(program) ?: arrayDt
- // turn the array literal it into an identifier reference
- val litval2 = array.cast(targetDt.getOrUndef())
- if(litval2!=null) {
- val vardecl2 = VarDecl.createAuto(litval2, targetDt.getOrUndef().isSplitWordArray)
- val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
- return listOf(
- IAstModification.ReplaceNode(array, identifier, parent),
- IAstModification.InsertFirst(vardecl2, array.definingScope)
- )
+ if((array.parent as? BinaryExpression)?.operator!="*") {
+ val parentAssign = parent as? Assignment
+ val targetDt = parentAssign?.target?.inferType(program) ?: arrayDt
+ // turn the array literal it into an identifier reference
+ val litval2 = array.cast(targetDt.getOrUndef())
+ if (litval2 != null) {
+ val vardecl2 = VarDecl.createAuto(litval2, targetDt.getOrUndef().isSplitWordArray)
+ val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
+ return listOf(
+ IAstModification.ReplaceNode(array, identifier, parent),
+ IAstModification.InsertFirst(vardecl2, array.definingScope)
+ )
+ }
}
}
}
diff --git a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt
index 5eb5ce797..0ecae698d 100644
--- a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt
+++ b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt
@@ -1,6 +1,7 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
+import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
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)
}
+ override fun after(array: ArrayLiteral, parent: Node): Iterable {
+ // 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(
range: RangeExpression,
fromConst: NumericLiteral?,
diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt
index 54f32daa6..3ced4659a 100644
--- a/compiler/test/TestCompilerOnExamples.kt
+++ b/compiler/test/TestCompilerOnExamples.kt
@@ -116,6 +116,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
"amiga",
"audioroutines",
"automatons",
+ "balloonflight",
"bdmusic",
"bobs",
"bubbleuniverse",
diff --git a/compiler/test/ast/TestAstChecks.kt b/compiler/test/ast/TestAstChecks.kt
index c98a670c9..102ac813d 100644
--- a/compiler/test/ast/TestAstChecks.kt
+++ b/compiler/test/ast/TestAstChecks.kt
@@ -104,7 +104,7 @@ class TestAstChecks: FunSpec({
val text = """
main {
sub start() {
- const ubyte[5] a = 5
+ const ubyte[5] a = [1,2,3,4,5]
a[2]=42
}
}
@@ -133,22 +133,6 @@ class TestAstChecks: FunSpec({
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") {
val text = """
%import floats
diff --git a/compiler/test/codegeneration/TestArrayThings.kt b/compiler/test/codegeneration/TestArrayThings.kt
index 72099cc11..a09cde735 100644
--- a/compiler/test/codegeneration/TestArrayThings.kt
+++ b/compiler/test/codegeneration/TestArrayThings.kt
@@ -4,7 +4,8 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
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.VMTarget
import prog8tests.helpers.ErrorReporterForTests
@@ -152,25 +153,6 @@ main {
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") {
val text = """
main {
@@ -329,79 +311,94 @@ main {
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="""
%option enable_floats
main {
sub start() {
- ubyte[] array = 1 to 4
- ubyte[] array2 = [1,2,3,4]
- str[] names = ["apple", "banana", "tomato"]
+ str name = "xyz" * 3
+ bool[3] boolarray = [true] * 3
+ 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!
- array = 20 to 23 ; ok!
- names = ["x1", "x2", "x3"] ; ok!
+ test("array initializer with range") {
+ val src="""
+%option enable_floats
- ubyte[] array3 = [1,2,3,4000] ; error: element type
- array = 10 to 15 ; error: array size
- array = 1000 to 1003 ; error: element type
- names = ["x1", "x2", "x3", "x4"] ; error: array size
- names = [1.1, 2.2, 3.3, 4.4] ; error: array size AND element type
- names = [1.1, 2.2, 999999.9] ; error: element type
- names = [1.1, 2.2, 9.9] ; error: element type
+main {
+ sub start() {
+ ubyte[3] bytearray2 = 10 to 12
+ uword[3] wordarray2 = 5000 to 5002
+ float[3] floatarray2 = 100 to 102
+ }
+}"""
+ 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()
+ }
+ array2.children.forEach {
+ it shouldBe instanceOf()
+ }
+ }
+
+ 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()
- compileText(C64Target(), false, src, writeAssembly = true, errors = errors) shouldBe null
- errors.errors.size shouldBe 8
- errors.errors[0] shouldContain "incompatible type"
- errors.errors[1] shouldContain "array size mismatch"
- 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"
+ compileText(C64Target(), false, src, writeAssembly = true, errors=errors) shouldBe null
+ errors.errors.size shouldBe 2
+ errors.errors[0] shouldContain "contains non-constant"
+ errors.errors[1] shouldContain "contains non-constant"
}
})
diff --git a/compiler/test/codegeneration/TestVariables.kt b/compiler/test/codegeneration/TestVariables.kt
index 4af007a5e..cb65c6575 100644
--- a/compiler/test/codegeneration/TestVariables.kt
+++ b/compiler/test/codegeneration/TestVariables.kt
@@ -5,7 +5,6 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.code.target.C64Target
-import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@@ -42,23 +41,6 @@ class TestVariables: FunSpec({
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") {
val text = """
main {
@@ -113,32 +95,4 @@ class TestVariables: FunSpec({
errors.errors[0] 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"
- }
})
diff --git a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt
index 23dfcbc18..245498b0c 100644
--- a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt
+++ b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt
@@ -293,16 +293,16 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(array: ArrayLiteral) {
- outputListMembers(array.value.asSequence())
+ outputListMembers(array.value)
}
- private fun outputListMembers(array: Sequence) {
+ private fun outputListMembers(array: Array) {
var counter = 0
output("[")
scopelevel++
- for (v in array) {
+ for ((idx, v) in array.withIndex()) {
v.accept(this)
- if (v !== array.last())
+ if (idx != array.size-1)
output(", ")
counter++
if (counter > 16) {
diff --git a/docs/source/programming.rst b/docs/source/programming.rst
index e2fdc0b05..d55d97c93 100644
--- a/docs/source/programming.rst
+++ b/docs/source/programming.rst
@@ -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[] 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]
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
@@ -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)
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)
- flags = [false, true] ; reset all flags in the array
.. note::
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
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.)
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
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,
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
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.
-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)::
+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)::
str string1 = "first part" + "second part"
str string2 = "hello!" * 10
diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst
index 941504f30..853dabb39 100644
--- a/docs/source/syntaxreference.rst
+++ b/docs/source/syntaxreference.rst
@@ -363,7 +363,7 @@ Various examples::
bool flag = true
byte[] values = [11, 22, 33, 44, 55]
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
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
=============== ======================= ================= =========================================
-**arrays:** you can split an array initializer list over several lines if you want. When an initialization
-value is given, the array size in the declaration can be omitted.
+**arrays:**
+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.
@@ -508,8 +511,6 @@ the downto variant to avoid having to specify the step as well::
xx = 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 {
; 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
^^^^^^
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).
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:
- ``"hello"`` a string translated into the default character encoding (PETSCII on the CBM machines)
diff --git a/docs/source/todo.rst b/docs/source/todo.rst
index 2037337c9..cd8555edb 100644
--- a/docs/source/todo.rst
+++ b/docs/source/todo.rst
@@ -1,6 +1,8 @@
TODO
====
+- fixup syscall list UNUSED_SYSCALL_1 and 2 (numbers shift!)
+
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 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
^^^^^^^^^^^^^^^^^^^^^^^
-
-- 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
-
+- keep boolean array intact in IR so that it might be represented as a bitmask in the resulting code (8 times storage improvement)
- 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
- Can we support signed % (remainder) somehow?
diff --git a/examples/c64/balloonflight.p8 b/examples/c64/balloonflight.p8
index 08767250b..3c299752d 100644
--- a/examples/c64/balloonflight.p8
+++ b/examples/c64/balloonflight.p8
@@ -1,8 +1,9 @@
%import syslib
%import textio
%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 {
@@ -37,11 +38,16 @@ main {
upwards = false
} else {
; determine new height for next mountain
- target_height = 9 + math.rnd() % 15
- if upwards
+ ubyte old_height = target_height
+ if upwards {
mountain = 233
- else
+ while target_height >= old_height
+ target_height = 9 + math.rnd() % 15
+ } else {
mountain = 223
+ while target_height <= old_height
+ target_height = 9 + math.rnd() % 15
+ }
}
while not do_char_scroll {
diff --git a/examples/cube3d-float.p8 b/examples/cube3d-float.p8
index 071ca8285..641cdf96c 100644
--- a/examples/cube3d-float.p8
+++ b/examples/cube3d-float.p8
@@ -12,9 +12,9 @@ main {
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
; storage for rotated coordinates
- float[len(xcoor)] rotatedx=0.0
- float[len(ycoor)] rotatedy=0.0
- float[len(zcoor)] rotatedz=-1.0
+ float[len(xcoor)] rotatedx
+ float[len(ycoor)] rotatedy
+ float[len(zcoor)] rotatedz
sub start() {
float time=0.0
diff --git a/examples/cx16/automatons.p8 b/examples/cx16/automatons.p8
index 179fe7b48..181ead3ca 100644
--- a/examples/cx16/automatons.p8
+++ b/examples/cx16/automatons.p8
@@ -25,7 +25,7 @@ main {
for y in 32 to 199+32 {
cx16.FB_cursor_position((320-len(cells))/2,y)
cx16.FB_set_pixels(cells, len(cells))
- cells_previous = cells
+ sys.memcopy(cells, cells_previous, sizeof(cells))
ubyte @zp x
for x in 0 to len(cells)-1 {
cells[x] = generate(x) ; next generation
diff --git a/examples/cx16/balloonflight.p8 b/examples/cx16/balloonflight.p8
new file mode 100644
index 000000000..3e5f9a270
--- /dev/null
+++ b/examples/cx16/balloonflight.p8
@@ -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
+ }}
+}
diff --git a/examples/test.p8 b/examples/test.p8
index af54f1ce6..423eb6014 100644
--- a/examples/test.p8
+++ b/examples/test.p8
@@ -1,42 +1,31 @@
-%import monogfx
+%import floats
%import textio
-%import math
-
%option no_sysinit
%zeropage basicsafe
-
main {
sub start() {
- monogfx.lores()
- demofill()
- }
+ uword[4] words1 = [1,2,3,4]
+ uword[4] words2 = [99,88,77,66]
- sub demofill() {
- const uword offsetx = 0
- const uword offsety = 0
-
- monogfx.circle(offsetx+160, offsety+120, 110, true)
- monogfx.rect(offsetx+180, offsety+5, 25, 190, true)
- monogfx.line(offsetx+100, offsety+150, offsetx+240, offsety+10, true)
- monogfx.line(offsetx+101, offsety+150, offsetx+241, offsety+10, true)
- monogfx.rect(offsetx+150, offsety+130, 10, 100, true)
-
- sys.wait(30)
-
- cbm.SETTIM(0,0,0)
- monogfx.fill(offsetx+100,offsety+100,true)
- monogfx.fill(offsetx+100,offsety+100,false)
- uword duration = cbm.RDTIM16()
- sys.wait(30)
-
- monogfx.textmode()
+ for cx16.r0 in words1 {
+ txt.print_uw(cx16.r0)
+ txt.spc()
+ }
+ txt.nl()
+ cx16.r0L = words2
+ sys.memsetw(words1, len(words1), 99)
+ for cx16.r0 in words1 {
+ txt.print_uw(cx16.r0)
+ txt.spc()
+ }
+ txt.nl()
+ sys.memcopy([2222,3333,4444,5555], words1, sizeof(words1))
+ for cx16.r0 in words1 {
+ txt.print_uw(cx16.r0)
+ txt.spc()
+ }
txt.nl()
- txt.print_uw(duration)
- txt.print(" jiffies\n")
-
- ; before optimizations: ~166 jiffies
-
}
}
diff --git a/gradle.properties b/gradle.properties
index 69cfb6a64..d51a58952 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,5 +4,5 @@ org.gradle.parallel=true
org.gradle.daemon=true
kotlin.code.style=official
javaVersion=11
-kotlinVersion=2.0.20
-version=10.4.2
+kotlinVersion=2.0.21
+version=10.5-SNAPSHOT
diff --git a/intermediate/src/prog8/intermediate/IMSyscall.kt b/intermediate/src/prog8/intermediate/IMSyscall.kt
index e3548e542..4b33373e2 100644
--- a/intermediate/src/prog8/intermediate/IMSyscall.kt
+++ b/intermediate/src/prog8/intermediate/IMSyscall.kt
@@ -18,7 +18,5 @@ enum class IMSyscall(val number: Int) {
CALLFAR(0x1017),
CALLFAR2(0x1018),
MEMCOPY(0x1019),
- MEMCOPY_SMALL(0x101a),
- ARRAYCOPY_SPLITW_TO_NORMAL(0x101b),
- ARRAYCOPY_NORMAL_TO_SPLITW(0x101c),
+ MEMCOPY_SMALL(0x101a)
}
diff --git a/scripts/cx16images.py b/scripts/cx16images.py
new file mode 100644
index 000000000..27d846524
--- /dev/null
+++ b/scripts/cx16images.py
@@ -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
diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt
index 0747d35bb..9c9ad1e9e 100644
--- a/virtualmachine/src/prog8/vm/SysCalls.kt
+++ b/virtualmachine/src/prog8/vm/SysCalls.kt
@@ -50,8 +50,8 @@ SYSCALLS:
37 = memset
38 = memsetw
39 = stringcopy
-40 = ARRAYCOPY_SPLITW_TO_NORMAL
-41 = ARRAYCOPY_NORMAL_TO_SPLITW
+40 = ...unused...
+41 = ...unused...
42 = memcopy_small
43 = load
44 = load_raw
@@ -103,8 +103,8 @@ enum class Syscall {
MEMSET,
MEMSETW,
STRINGCOPY,
- ARRAYCOPY_SPLITW_TO_NORMAL,
- ARRAYCOPY_NORMAL_TO_SPLITW,
+ UNUSED_SYSCALL_1, // TODO fixup
+ UNUSED_SYSCALL_2, // TODO fixup
MEMCOPY_SMALL,
LOAD,
LOAD_RAW,
@@ -444,29 +444,6 @@ object SysCalls {
vm.memory.setString(target, string, true)
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.. {
- 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.. {
val (filenameA, addrA) = getArgValues(callspec.arguments, vm)
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
}
+
+ Syscall.UNUSED_SYSCALL_1 -> TODO("remove this")
+ Syscall.UNUSED_SYSCALL_2 -> TODO("remove this")
}
}
}
diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt
index 87d0bdf6d..2137c0efd 100644
--- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt
+++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt
@@ -115,8 +115,6 @@ class VmProgramLoader {
IMSyscall.CALLFAR2.number -> throw IRParseException("vm doesn't support the callfar2() syscall")
IMSyscall.MEMCOPY.number -> Syscall.MEMCOPY
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
}