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:
Irmen de Jong 2024-10-13 21:48:33 +02:00
commit 0f72bdb4a2
33 changed files with 856 additions and 583 deletions

View File

@ -1,23 +1,23 @@
<component name="libraryTable">
<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>
<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/2.0.20/kotlin-stdlib-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.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/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>
<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/2.0.20/kotlin-stdlib-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.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/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>
<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/2.0.20/kotlin-stdlib-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.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/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>
</library>
</component>

View File

@ -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")

View File

@ -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)

View File

@ -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 -> "<arrayindexer> ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}"
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
is PtBuiltinFunctionCall -> {

View File

@ -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" 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),

View File

@ -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 -> {

View File

@ -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 {

View File

@ -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<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 {
val result = mutableListOf<IRCodeChunkBase>()
val valueTr = exprGen.translateExpression(call.args[0])

View File

@ -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<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) {
val leftExpr = expr.left as? BinaryExpression
// only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements)

View File

@ -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<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
val dt = decl.datatype
when {
dt.isUnsignedByteArray || dt.isSignedByteArray || dt.isUnsignedWordArray || dt.isSignedWordArray -> {
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<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
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!!)
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(),
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<Expression>()
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<Expression>()
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

View File

@ -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)

View File

@ -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<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> {
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {

View File

@ -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
}

View File

@ -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)
)
}
}
}
}

View File

@ -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<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(
range: RangeExpression,
fromConst: NumericLiteral?,

View File

@ -116,6 +116,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
"amiga",
"audioroutines",
"automatons",
"balloonflight",
"bdmusic",
"bobs",
"bubbleuniverse",

View File

@ -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

View File

@ -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<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()
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"
}
})

View File

@ -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"
}
})

View File

@ -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<Expression>) {
private fun outputListMembers(array: Array<Expression>) {
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) {

View File

@ -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

View File

@ -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)

View File

@ -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?

View File

@ -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 {

View File

@ -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

View File

@ -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

View 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
}}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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)
}

298
scripts/cx16images.py Normal file
View 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

View File

@ -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..<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 -> {
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")
}
}
}

View File

@ -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
}