improved array literal datatype handling, fixed some datatype compiler errors related to this

This commit is contained in:
Irmen de Jong 2020-03-12 01:10:19 +01:00
parent fbecedaf41
commit 5f1ec80ae0
15 changed files with 114 additions and 229 deletions

View File

@ -476,7 +476,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
val array = litval.arrayliteral().toAst()
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFold takes care of that and converts the type if needed.
ArrayLiteralValue(DataType.ARRAY_UB, array, position = litval.toPosition())
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
}
litval.structliteral()!=null -> {
val values = litval.structliteral().expression().map { it.toAst() }

View File

@ -126,6 +126,13 @@ val ArrayElementTypes = mapOf(
DataType.ARRAY_W to DataType.WORD,
DataType.ARRAY_UW to DataType.UWORD,
DataType.ARRAY_F to DataType.FLOAT)
val ElementArrayTypes = mapOf(
DataType.BYTE to DataType.ARRAY_B,
DataType.UBYTE to DataType.ARRAY_UB,
DataType.WORD to DataType.ARRAY_W,
DataType.UWORD to DataType.ARRAY_UW,
DataType.FLOAT to DataType.ARRAY_F
)
// find the parent node of a specific type or interface
// (useful to figure out in what namespace/block something is defined, etc)

View File

@ -5,10 +5,7 @@ import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
@ -454,7 +451,7 @@ class StringLiteralValue(val value: String,
}
}
class ArrayLiteralValue(val type: DataType, // only array types
class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred because not all array literals hava a known type yet
val value: Array<Expression>,
override val position: Position) : Expression() {
override lateinit var parent: Node
@ -470,7 +467,8 @@ class ArrayLiteralValue(val type: DataType, // only array types
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String = "$value"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isUnknown) type else guessDatatype(program)
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
override fun hashCode(): Int = Objects.hash(value, type)
override fun equals(other: Any?): Boolean {
@ -479,8 +477,36 @@ class ArrayLiteralValue(val type: DataType, // only array types
return type==other.type && value.contentEquals(other.value)
}
fun guessDatatype(program: Program): InferredTypes.InferredType {
// Educated guess of the desired array literal's datatype.
// If it's inside a for loop, assume the data type of the loop variable is what we want.
val forloop = parent as? ForLoop
if(forloop != null) {
val loopvarDt = forloop.loopVarDt(program)
if(loopvarDt.isKnown) {
return if(loopvarDt.typeOrElse(DataType.STRUCT) !in ElementArrayTypes)
InferredTypes.InferredType.unknown()
else
InferredTypes.InferredType.known(ElementArrayTypes.getValue(loopvarDt.typeOrElse(DataType.STRUCT)))
}
}
// otherwise, select the "biggegst" datatype based on the elements in the array.
val datatypesInArray = value.map { it.inferType(program) }
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
else -> InferredTypes.InferredType.unknown()
}
}
fun cast(targettype: DataType): ArrayLiteralValue? {
if(type==targettype)
if(type.istype(targettype))
return this
if(targettype in ArrayDatatypes) {
val elementType = ArrayElementTypes.getValue(targettype)
@ -499,7 +525,7 @@ class ArrayLiteralValue(val type: DataType, // only array types
}
}
}.toTypedArray()
return ArrayLiteralValue(targettype, castArray, position = position)
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
}
return null // invalid type conversion from $this to $targettype
}

View File

@ -686,11 +686,13 @@ internal class AstChecker(private val program: Program,
}
override fun visit(array: ArrayLiteralValue) {
if(!compilerOptions.floats && array.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
if(array.type.isKnown) {
if (!compilerOptions.floats && array.type.typeOrElse(DataType.STRUCT) in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", array.position))
}
val arrayspec = ArrayIndex.forArray(array)
checkValueTypeAndRangeArray(array.type, null, arrayspec, array)
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array)
}
super.visit(array)
}
@ -1064,11 +1066,15 @@ internal class AstChecker(private val program: Program,
checkResult.add(ExpressionError(msg, value.position))
return false
}
if(value.type.isUnknown)
return err("attempt to check values of array with as yet unknown datatype")
when (targetDt) {
DataType.STR -> return err("string value expected")
DataType.ARRAY_UB, DataType.ARRAY_B -> {
// value may be either a single byte, or a byte arraysize (of all constant values), or a range
if(value.type==targetDt) {
if(value.type.istype(targetDt)) {
if(!checkArrayValues(value, targetDt))
return false
val arraySpecSize = arrayspec.size()
@ -1090,7 +1096,7 @@ internal class AstChecker(private val program: Program,
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
// value may be either a single word, or a word arraysize, or a range
if(value.type==targetDt) {
if(value.type.istype(targetDt)) {
if(!checkArrayValues(value, targetDt))
return false
val arraySpecSize = arrayspec.size()
@ -1112,7 +1118,7 @@ internal class AstChecker(private val program: Program,
}
DataType.ARRAY_F -> {
// value may be either a single float, or a float arraysize
if(value.type==targetDt) {
if(value.type.istype(targetDt)) {
if(!checkArrayValues(value, targetDt))
return false
val arraySize = value.value.size
@ -1138,7 +1144,7 @@ internal class AstChecker(private val program: Program,
return err("invalid float array initialization value ${value.type}, expected $targetDt")
}
DataType.STRUCT -> {
if(value.type in ArrayDatatypes) {
if(value.type.typeOrElse(DataType.STRUCT) in ArrayDatatypes) {
if(value.value.size != struct!!.numberOfElements)
return err("number of values is not the same as the number of members in the struct")
for(elt in value.value.zip(struct.statements)) {

View File

@ -225,24 +225,36 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
return super.visit(returnStmt)
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
val vardecl = array.parent as? VarDecl
return if(vardecl!=null)
fixupArrayEltDatatypesFromVardecl(array, vardecl)
// adjust the datatype of the array (to an educated guess)
if(vardecl!=null) {
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null) {
vardecl.value = cast
cast.linkParents(vardecl)
return cast
}
}
return array
}
else {
// fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
// (we don't know the desired datatype here exactly so we guess)
val datatype = determineArrayDt(array.value)
val litval2 = array.cast(datatype)
if(litval2!=null) {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
return if (litval2 != null) {
litval2.parent = array.parent
// finally, replace the literal array by a identifier reference.
makeIdentifierFromRefLv(litval2)
} else array
}
}
}
return array
}
@ -261,20 +273,6 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
return string
}
private fun determineArrayDt(array: Array<Expression>): DataType {
val datatypesInArray = array.map { it.inferType(program) }
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
DataType.UBYTE in dts -> DataType.ARRAY_UB
else -> throw IllegalArgumentException("can't determine type of array")
}
}
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value
@ -350,46 +348,3 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
}
}
internal fun fixupArrayEltDatatypes(array: ArrayLiteralValue, program: Program): ArrayLiteralValue {
val dts = array.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
if(dts.any { it !in NumericDatatypes }) {
return array
}
val dt = when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
else -> DataType.ARRAY_UB
}
if(dt==array.type)
return array
// convert values and array type
val elementType = ArrayElementTypes.getValue(dt)
val allNumerics = array.value.all { it is NumericLiteralValue }
if(allNumerics) {
val values = array.value.map { (it as NumericLiteralValue).cast(elementType) as Expression }.toTypedArray()
val array2 = ArrayLiteralValue(dt, values, array.position)
array2.linkParents(array.parent)
return array2
}
return array
}
internal fun fixupArrayEltDatatypesFromVardecl(array: ArrayLiteralValue, vardecl: VarDecl): ArrayLiteralValue {
val arrayDt = array.type
if(arrayDt!=vardecl.datatype) {
// fix the datatype of the array (also on the heap) to match the vardecl
val cast = array.cast(vardecl.datatype)
if (cast != null) {
vardecl.value = cast
cast.linkParents(vardecl)
return cast
}
// can't be casted yet, attempt again later
}
return array
}

View File

@ -42,7 +42,7 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
if(decl.isArray && decl.value==null) {
// array datatype without initialization value, add list of zeros
val arraysize = decl.arraysize!!.size()!!
val array = ArrayLiteralValue(decl.datatype,
val array = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
decl.position)
decl.value = array

View File

@ -199,7 +199,7 @@ class VarDecl(val type: VarDeclType,
fun createAuto(array: ArrayLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val declaredType = ArrayElementTypes.getValue(array.type)
val declaredType = ArrayElementTypes.getValue(array.type.typeOrElse(DataType.STRUCT))
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
isArray = true, autogeneratedDontRemove = true, position = array.position)
@ -663,6 +663,12 @@ class ForLoop(val loopRegister: Register?,
override fun toString(): String {
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
}
fun loopVarDt(program: Program): InferredTypes.InferredType {
val lv = loopVar
return if(loopRegister!=null) InferredTypes.InferredType.known(DataType.UBYTE)
else lv?.inferType(program) ?: InferredTypes.InferredType.unknown()
}
}
class WhileLoop(var condition: Expression,

View File

@ -5,8 +5,6 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.fixupArrayEltDatatypesFromVardecl
import prog8.ast.processing.fixupArrayEltDatatypes
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
@ -76,11 +74,11 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
if(eltType in ByteDatatypes) {
decl.value = ArrayLiteralValue(decl.datatype,
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
decl.value = ArrayLiteralValue(decl.datatype,
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
@ -116,7 +114,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(decl.datatype, array, position = numericLv.position)
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
decl.value = refValue
refValue.parent=decl
optimizationsDone++
@ -137,7 +135,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(DataType.ARRAY_F, array, position = litval.position)
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
decl.value = refValue
refValue.parent=decl
optimizationsDone++
@ -629,14 +627,18 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
// because constant folding can result in arrays that are now suddenly capable
// of telling the type of all their elements (for instance, when they contained -2 which
// was a prefix expression earlier), we recalculate the array's datatype.
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
val vardecl = array.parent as? VarDecl
return if (vardecl!=null) {
fixupArrayEltDatatypesFromVardecl(array, vardecl)
} else {
// it's not an array associated with a vardecl, attempt to guess the data type from the array values
fixupArrayEltDatatypes(array, program)
if(array.type.isKnown)
return array
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
val newArray = arrayLiteral.cast(arrayDt.typeOrElse(DataType.STRUCT))
if(newArray!=null)
return newArray
}
}
return array

View File

@ -22,7 +22,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
override fun visit(assignment: Assignment): Statement {
if (assignment.aug_op != null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
return super.visit(assignment)
}

View File

@ -424,7 +424,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
override fun visit(assignment: Assignment): Statement {
if(assignment.aug_op!=null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace)) {

View File

@ -603,9 +603,9 @@ open class RuntimeValueArray(type: DataType, val array: Array<Number>, val heapI
companion object {
fun fromLv(array: ArrayLiteralValue): RuntimeValueArray {
return if (array.type == DataType.ARRAY_F) {
return if (array.type.istype(DataType.ARRAY_F)) {
val doubleArray = array.value.map { (it as NumericLiteralValue).number }.toTypedArray()
RuntimeValueArray(array.type, doubleArray, array.heapId)
RuntimeValueArray(DataType.ARRAY_F, doubleArray, array.heapId)
} else {
val resultArray = mutableListOf<Number>()
for (elt in array.value.withIndex()) {
@ -615,7 +615,7 @@ open class RuntimeValueArray(type: DataType, val array: Array<Number>, val heapI
resultArray.add((elt.hashCode())) // ...poor man's implementation of ADDRESSOF(array), it probably won't work very well
}
}
RuntimeValueArray(array.type, resultArray.toTypedArray(), array.heapId)
RuntimeValueArray(array.type.typeOrElse(DataType.STRUCT), resultArray.toTypedArray(), array.heapId)
}
}
}

View File

@ -5,6 +5,7 @@ import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import kotlin.test.assertEquals
@ -96,9 +97,9 @@ class TestParserNumericLiteralValue {
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
val lv1 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOne, lvTwo, lvThree), dummyPos)
val lv2 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
val lv3 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
val lv1 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOne, lvTwo, lvThree), dummyPos)
val lv2 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
val lv3 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
assertEquals(lv1, lv2)
assertNotEquals(lv1, lv3)
}

View File

@ -5,125 +5,7 @@ main {
sub start() {
ubyte[] uba = [1,2,3]
byte[] bba = [1,2,3]
uword[] uwa = [1111,2222,3333]
word[] wwa = [1111,2222,3333]
ubyte ub
byte bb
uword uw
word ww
for ub in uba {
c64scr.print_ub(ub)
c64scr.print(",")
}
c64scr.print("\n")
for bb in bba {
c64scr.print_b(bb)
c64scr.print(",")
}
c64scr.print("\n")
for uw in uwa {
c64scr.print_uw(uw)
c64scr.print(",")
}
c64scr.print("\n")
for ww in wwa {
c64scr.print_w(ww)
c64scr.print(",")
}
c64scr.print("\n")
for ub in [1,2,3] {
c64scr.print_ub(ub)
c64scr.print(",")
}
; c64scr.print("\n")
; for bb in [1,2,3] { ; TODO fix array literal conversion error
; c64scr.print_b(bb)
; c64scr.print(",")
; }
c64scr.print("\n")
for uw in [1111,2222,3333] {
c64scr.print_uw(uw)
c64scr.print(",")
}
; c64scr.print("\n")
; for ww in [1111,2222,3333] { ; TODO fix array literal conversion error
; c64scr.print_w(ww)
; c64scr.print(",")
; }
c64scr.print("\n")
ubyte[] ubb1 = [ 1 ]
ubyte[] ubb2 = [ 1, 2]
ubyte[] ubb3 = [ 1,2,3 ]
ubyte[] ubb4 = [ 1,2,3,4 ]
uword[] uww1 = [111]
uword[] uww2 = [111,222]
uword[] uww3 = [111,222,333]
uword[] uww4 = [111,222,333,444]
reverse(ubb1)
reverse(ubb2)
reverse(ubb3)
reverse(ubb4)
reverse(uww1)
reverse(uww2)
reverse(uww3)
reverse(uww4)
for ub in ubb1 {
c64scr.print_ub(ub)
c64scr.print(",")
}
c64scr.print("\n")
for ub in ubb2 {
c64scr.print_ub(ub)
c64scr.print(",")
}
c64scr.print("\n")
for ub in ubb3 {
c64scr.print_ub(ub)
c64scr.print(",")
}
c64scr.print("\n")
for ub in ubb4 {
c64scr.print_ub(ub)
c64scr.print(",")
}
c64scr.print("\n")
for uw in uww1 {
c64scr.print_uw(uw)
c64scr.print(",")
}
c64scr.print("\n")
for uw in uww2 {
c64scr.print_uw(uw)
c64scr.print(",")
}
c64scr.print("\n")
for uw in uww3 {
c64scr.print_uw(uw)
c64scr.print(",")
}
c64scr.print("\n")
for uw in uww4 {
c64scr.print_uw(uw)
c64scr.print(",")
}
c64scr.print("\n")
float[] fa = [1.1, 2.2, 3.3, 4.4, 5.5]
reverse(fa)
sort(uww3)
sort(fa)
for ub in 0 to len(fa)-1 {
c64flt.print_f(fa[ub])
c64scr.print(",")
}
c64scr.print("\n")
byte[] barr = [22,-33,-44,55,66]
}
}

View File

@ -12,7 +12,7 @@ main {
return
str s1 = "hello"
str s2 = "screencodes" ; TODO as c64sc
str s2 = @"screencodes"
&str ms1 = $c000

View File

@ -414,7 +414,7 @@ main {
count = 0
total = 0
c64scr.print("byte var in arrayliteral: ")
for bb in [1,3,5,99] { ; TODO now gives compiler error, fix byte var array literal conversion
for bb in [1,3,5,99] {
count++
total += bb
}
@ -790,7 +790,7 @@ main {
count = 0
total = 0
c64scr.print("word var in arrayliteral: ")
for ww in [1111,3333,555,999] { ; TODO now compiler error, fix word var array literal conversion
for ww in [1111,3333,555,999] {
count++
total += ww
}