diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 124260855..b20d52dc8 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -8,32 +8,32 @@ sub start() { - byte[3] barr1 = [-2,-1,2] - byte[3] barr2 = [-22,-1, 3] - ubyte[3] barr3 = [-2,-1,2] ; @ todo error - ubyte[3] barr4 = [1,2,33] - ubyte[3] barr5 = [1,2,-33] ; @todo error - word[3] warr1 = [-2,-1,2] - ;word[3] warr2 = [-2,-1,2, 3453] ; @todo ok - uword[3] warr3 = [-2,-1,2] - uword[3] warr4 = [1,2,33.w] - uword[3] warr5 = [1,2,-33] ; @todo error + byte[4] barr1 = -2 + byte[4] barr2 = 33 + ubyte[4] barr4 = 2 + ubyte[4] barr5 = 44 + word[4] warr1 = 4444 + word[4] warr2 = -5555 + word[4] warr2b = -5522 + float[4] farr1 + float[4] farr2 = 23 + float[4] farr3 = 55.636346 - byte b1 = 50 * 2 - byte b2 = -50 * 2 - ubyte ub1 = 50 * 2 - word w1 = 999 * 2 - word w2 = -999 * 2 - uword uw1 = 999 * 2 - float f1 = 999*2 - float f2 = -999*2 +; byte b1 = 50 * 2 +; byte b2 = -50 * 2 +; ubyte ub1 = 50 * 2 +; word w1 = 999 * 2 +; word w2 = -999 * 2 +; uword uw1 = 999 * 2 +; float f1 = 999*2 +; float f2 = -999*2 return } - sub toscreenx(x: float, z: float) -> word { - return floor(x/(4.2+z) * flt(height)) + width // 2 - } +; sub toscreenx(x: float, z: float) -> word { +; return floor(x/(4.2+z) * flt(height)) + width // 2 +; } } diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index fc3f64c77..65908df33 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -560,6 +560,13 @@ class ArraySpec(var x: IExpression, var y: IExpression?, override val position: y?.linkParents(this) } + companion object { + fun forArray(v: LiteralValue, heap: HeapValues): ArraySpec { + val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize + return ArraySpec(LiteralValue.optimalInteger(arraySize, v.position), null, v.position) + } + } + fun process(processor: IAstProcessor) { x = x.process(processor) y = y?.process(processor) @@ -1042,7 +1049,12 @@ class LiteralValue(val type: DataType, val fh = floatvalue?.hashCode() ?: 0x00103456 val sh = strvalue?.hashCode() ?: 0x00014567 val ah = arrayvalue?.hashCode() ?: 0x11119876 - return bh xor wh xor fh xor sh xor ah xor type.hashCode() + var hash = bh * 31 xor wh + hash = hash*31 xor fh + hash = hash*31 xor sh + hash = hash*31 xor ah + hash = hash*31 xor type.hashCode() + return hash } override fun equals(other: Any?): Boolean { @@ -1906,7 +1918,7 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression { litval.charliteral()!=null -> LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(litval.charliteral().text.unescape(), true)[0], position = litval.toPosition()) litval.arrayliteral()!=null -> { val array = litval.arrayliteral()?.toAst() - // byte/word array type difference is not determined here. + // the actual type of the array can not yet be determined here (missing namespace & heap) // the ConstantFolder takes care of that and converts the type if needed. LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition()) } diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index fa9a8ccb5..b2f4bbe1d 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -415,9 +415,15 @@ class AstChecker(private val namespace: INameScope, } when { decl.value is RangeExpr -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as RangeExpr) - decl.value is LiteralValue -> checkValueTypeAndRange(decl.datatype, - decl.arrayspec ?: ArraySpec(LiteralValue.optimalInteger(-2, decl.position), null, decl.position), - decl.value as LiteralValue, heap) + decl.value is LiteralValue -> { + val arraySpec = decl.arrayspec ?: ( + if((decl.value as LiteralValue).isArray) + ArraySpec.forArray(decl.value as LiteralValue, heap) + else + ArraySpec(LiteralValue.optimalInteger(-2, decl.position), null, decl.position) + ) + checkValueTypeAndRange(decl.datatype, arraySpec, decl.value as LiteralValue, heap) + } else -> { err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}") return super.process(decl) @@ -515,9 +521,12 @@ class AstChecker(private val namespace: INameScope, if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) { checkResult.add(SyntaxError("floating point value used, but floating point is not enabled via options", literalValue.position)) } - checkValueTypeAndRange(literalValue.type, - ArraySpec(LiteralValue.optimalInteger(-3, literalValue.position), null, literalValue.position), - literalValue, heap) + val arrayspec = + if(literalValue.isArray) + ArraySpec.forArray(literalValue, heap) + else + ArraySpec(LiteralValue.optimalInteger(-3, literalValue.position), null, literalValue.position) + checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, heap) val lv = super.process(literalValue) when(lv.type) { @@ -798,7 +807,7 @@ class AstChecker(private val namespace: INameScope, } DataType.ARRAY_UB, DataType.ARRAY_B -> { // value may be either a single byte, or a byte array (of all constant values) - if(value.type==DataType.ARRAY_UB || value.type==DataType.ARRAY_B) { + if(value.type==targetDt) { val arraySize = value.arrayvalue?.size ?: heap.get(value.heapId!!).array!!.size val arraySpecSize = arrayspec.size() if(arraySpecSize!=null && arraySpecSize>0) { @@ -808,66 +817,34 @@ class AstChecker(private val namespace: INameScope, val expectedSize = constX.asIntegerValue if (arraySize != expectedSize) return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)") - } - } else if(value.type==DataType.ARRAY_UW || value.type==DataType.ARRAY_W) { - return err("initialization value must be an array of bytes") - } else { - if(targetDt==DataType.ARRAY_UB) { - val number = value.bytevalue ?: return if (value.floatvalue != null) - err("unsigned byte value expected instead of float; possible loss of precision") - else - err("unsigned byte value expected") - if (number < 0 || number > 255) - return err("value '$number' out of range for unsigned byte") - } else { - val number = value.bytevalue ?: return if (value.floatvalue != null) - err("byte value expected instead of float; possible loss of precision") - else - err("byte value expected") - if (number < -128 || number > 127) - return err("value '$number' out of range for byte") + return true } } + return err("invalid array initialization value ${value.type}, expected $targetDt") } DataType.ARRAY_UW, DataType.ARRAY_W -> { // value may be either a single word, or a word array - if(value.type==DataType.ARRAY_UB || value.type==DataType.ARRAY_UW || value.type==DataType.ARRAY_B || value.type==DataType.ARRAY_W) { + if(value.type==targetDt) { val arraySize = value.arrayvalue?.size ?: heap.get(value.heapId!!).array!!.size val arraySpecSize = arrayspec.size() if(arraySpecSize!=null && arraySpecSize>0) { - // arrayspec is not always known when checking val constX = arrayspec.x.constValue(namespace, heap) if(constX?.asIntegerValue==null) return err("array size specifier must be constant integer value") val expectedSize = constX.asIntegerValue if (arraySize != expectedSize) return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)") - } - } else { - if(targetDt==DataType.ARRAY_UW) { - val number = value.asIntegerValue ?: return if (value.floatvalue != null) - err("unsigned byte or word value expected instead of float; possible loss of precision") - else - err("unsigned byte or word value expected") - if (number < 0 || number > 65535) - return err("value '$number' out of range for unsigned word") - } else { - val number = value.asIntegerValue ?: return if (value.floatvalue != null) - err("byte or word value expected instead of float; possible loss of precision") - else - err("byte or word value expected") - if (number < -32768 || number > 32767) - return err("value '$number' out of range for word") + return true } } + return err("invalid array initialization value ${value.type}, expected $targetDt") } DataType.ARRAY_F -> { // value may be either a single float, or a float array - if(value.type==DataType.ARRAY_UB || value.type==DataType.ARRAY_UW || value.type==DataType.ARRAY_F) { - val arraySize = value.arrayvalue?.size ?: heap.get(value.heapId!!).arraysize + if(value.type==targetDt) { + val arraySize = value.arrayvalue?.size ?: heap.get(value.heapId!!).doubleArray!!.size val arraySpecSize = arrayspec.size() if(arraySpecSize!=null && arraySpecSize>0) { - // arrayspec is not always known when checking val constX = arrayspec.x.constValue(namespace, heap) if(constX?.asIntegerValue==null) return err("array size specifier must be constant integer value") @@ -875,17 +852,20 @@ class AstChecker(private val namespace: INameScope, if (arraySize != expectedSize) return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)") } - } else { - val number = value.asNumericValue?.toDouble() - if(number==null) - return err("expected numerical value") - else if (number < FLOAT_MAX_NEGATIVE || number > FLOAT_MAX_POSITIVE) - return err("value '$number' out of range for mfplt5 floating point") + // check if the floating point values are all within range + val doubles = if(value.arrayvalue!=null) + value.arrayvalue.map {it.constValue(namespace, heap)?.asNumericValue!!.toDouble()}.toDoubleArray() + else + heap.get(value.heapId!!).doubleArray!! + if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE}) + return err("floating point value overflow") + return true } + return err("invalid array initialization value ${value.type}, expected $targetDt") } DataType.MATRIX_UB, DataType.MATRIX_B -> { // value can only be a single byte, or a byte array (which represents the matrix) - if(value.type==DataType.ARRAY_UB || value.type==DataType.ARRAY_B || value.type==DataType.MATRIX_UB || value.type==DataType.MATRIX_B) { + if(value.type==targetDt) { val arraySpecSize = arrayspec.size() if(arraySpecSize!=null && arraySpecSize>0) { val constX = arrayspec.x.constValue(namespace, heap) @@ -897,19 +877,8 @@ class AstChecker(private val namespace: INameScope, if (matrix.size != expectedSize) return err("initializer matrix size mismatch (expecting $expectedSize, got ${matrix.size} elements)") } - } else { - if(targetDt==DataType.MATRIX_UB) { - val number = value.bytevalue - ?: return err("unsigned byte value expected") - if (number < 0 || number > 255) - return err("value '$number' out of range for unsigned byte") - } else { - val number = value.bytevalue - ?: return err("byte value expected") - if (number < -128 || number > 127) - return err("value '$number' out of range for byte") - } } + return err("invalid matrix initialization value $value") } } return true diff --git a/compiler/src/prog8/optimizing/ConstantFolding.kt b/compiler/src/prog8/optimizing/ConstantFolding.kt index 15776793b..d2ff602c3 100644 --- a/compiler/src/prog8/optimizing/ConstantFolding.kt +++ b/compiler/src/prog8/optimizing/ConstantFolding.kt @@ -1,6 +1,7 @@ package prog8.optimizing import prog8.ast.* +import prog8.compiler.CompilerException import prog8.compiler.HeapValues import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE @@ -54,6 +55,8 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV } DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.MATRIX_UB, DataType.MATRIX_B -> { val litval = decl.value as? LiteralValue + if(litval?.type==DataType.FLOAT) + errors.add(ExpressionError("array requires only integers here", litval.position)) val size = decl.arrayspec!!.size() if(litval!=null && litval.isArray) { // array initializer value is an array already, keep as-is (or convert to WORDs if needed) @@ -67,11 +70,36 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV heap.update(litval.heapId, HeapValues.HeapValue(DataType.ARRAY_UW, null, array.array, null)) decl.value = LiteralValue(decl.datatype, heapId = litval.heapId, position = litval.position) } + } else if(decl.datatype==DataType.ARRAY_W && litval.type == DataType.ARRAY_B) { + val array = heap.get(litval.heapId) + if(array.array!=null) { + heap.update(litval.heapId, HeapValues.HeapValue(DataType.ARRAY_W, null, array.array, null)) + decl.value = LiteralValue(decl.datatype, heapId = litval.heapId, position = litval.position) + } } } } else if (size != null) { // array initializer is empty or a single int, and we know the size; create the array. val fillvalue = if (litval == null) 0 else litval.asIntegerValue ?: 0 + when(decl.datatype){ + DataType.ARRAY_UB, DataType.MATRIX_UB -> { + if(fillvalue !in 0..255) + errors.add(ExpressionError("ubyte value overflow", litval?.position ?: decl.position)) + } + DataType.ARRAY_B, DataType.MATRIX_B -> { + if(fillvalue !in -128..127) + errors.add(ExpressionError("byte value overflow", litval?.position ?: decl.position)) + } + DataType.ARRAY_UW -> { + if(fillvalue !in 0..65535) + errors.add(ExpressionError("uword value overflow", litval?.position ?: decl.position)) + } + DataType.ARRAY_W -> { + if(fillvalue !in -32768..32767) + errors.add(ExpressionError("word value overflow", litval?.position ?: decl.position)) + } + else -> {} + } val fillArray = IntArray(size) { _ -> fillvalue } val heapId = heap.add(decl.datatype, fillArray) decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position) @@ -93,9 +121,13 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV } else if (size != null) { // array initializer is empty or a single int, and we know the size; create the array. val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0 - val fillArray = DoubleArray(size) { _ -> fillvalue } - val heapId = heap.add(decl.datatype, fillArray) - decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position) + if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE) + errors.add(ExpressionError("float value overflow", litval?.position ?: decl.position)) + else { + val fillArray = DoubleArray(size) { _ -> fillvalue } + val heapId = heap.add(decl.datatype, fillArray) + decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position) + } } } else -> return result @@ -437,100 +469,63 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV return super.process(newValue) } } else if(literalValue.arrayvalue!=null) { - val newArray = literalValue.arrayvalue.map { it.process(this) }.toTypedArray() - val arrayDt = - if(newArray.any { it.resultingDatatype(namespace, heap) == DataType.FLOAT }) - DataType.ARRAY_F - else { - if (newArray.any { it.resultingDatatype(namespace, heap) in setOf(DataType.BYTE, DataType.WORD) }) { - // we have signed values - if (newArray.any { it.resultingDatatype(namespace, heap) == DataType.WORD }) - DataType.ARRAY_W - else - DataType.ARRAY_B - } else { - // only unsigned values - if (newArray.any { it.resultingDatatype(namespace, heap) == DataType.UWORD }) - DataType.ARRAY_UW - else DataType.ARRAY_UB - } - } - - // if the values are all constants, the array is moved to the heap - val allElementsAreConstant = newArray.fold(true) { c, expr-> c and (expr is LiteralValue)} - if(allElementsAreConstant) { - val litArray = newArray.map{ it as? LiteralValue } - if(null in litArray) { - addError(ExpressionError("array/matrix literal can contain only constant values", literalValue.position)) - return super.process(literalValue) - } - if(arrayDt==DataType.ARRAY_UB || arrayDt==DataType.MATRIX_UB) { - // all values should be ubytes - val integerArray = litArray.map { (it as LiteralValue).bytevalue } - if (integerArray.any { it == null || it.toInt() !in 0..255 }) { - addError(ExpressionError("byte array elements must all be integers 0..255", literalValue.position)) - return super.process(literalValue) - } - val array = integerArray.mapNotNull { it?.toInt() }.toIntArray() - val heapId = heap.add(arrayDt, array) - val newValue = LiteralValue(arrayDt, heapId = heapId, position = literalValue.position) - return super.process(newValue) - } else if(arrayDt==DataType.ARRAY_B || arrayDt==DataType.MATRIX_B) { - // all values should be bytes - val integerArray = litArray.map { (it as LiteralValue).bytevalue } - if(integerArray.any { it==null || it.toInt() !in -128..127 }) { - addError(ExpressionError("byte array elements must all be integers -128..127", literalValue.position)) - return super.process(literalValue) - } - val array = integerArray.mapNotNull { it?.toInt() }.toIntArray() - val heapId = heap.add(arrayDt, array) - val newValue = LiteralValue(arrayDt, heapId=heapId, position = literalValue.position) - return super.process(newValue) - } else if(arrayDt==DataType.ARRAY_UW) { - // all values should be bytes or words - val integerArray = litArray.map { (it as LiteralValue).asIntegerValue } - if(integerArray.any {it==null || it !in 0..65535 }) { - addError(ExpressionError("word array elements must all be integers 0..65535", literalValue.position)) - return super.process(literalValue) - } - val array = integerArray.filterNotNull().toIntArray() - val heapId = heap.add(arrayDt, array) - val newValue = LiteralValue(DataType.ARRAY_UW, heapId=heapId, position = literalValue.position) - return super.process(newValue) - } else if(arrayDt==DataType.ARRAY_W) { - // all values should be bytes or words - val integerArray = litArray.map { (it as LiteralValue).asIntegerValue } - if(integerArray.any {it==null || it !in -32768..32767 }) { - addError(ExpressionError("word array elements must all be integers -32768..32767", literalValue.position)) - return super.process(literalValue) - } - val array = integerArray.filterNotNull().toIntArray() - val heapId = heap.add(arrayDt, array) - val newValue = LiteralValue(DataType.ARRAY_W, heapId=heapId, position = literalValue.position) - return super.process(newValue) - } else if(arrayDt==DataType.ARRAY_F) { - // all values should be bytes, words or floats - val doubleArray = litArray.map { (it as LiteralValue).asNumericValue?.toDouble() } - if(doubleArray.any { it==null || it !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE }) { - addError(ExpressionError("float array elements must all be floats in the acceptable range", literalValue.position)) - return super.process(literalValue) - } - val array = doubleArray.filterNotNull().toDoubleArray() - val heapId = heap.add(arrayDt, array) - val newValue = LiteralValue(DataType.ARRAY_F, heapId=heapId, position = literalValue.position) - return super.process(newValue) - } - } else { - addError(ExpressionError("array/matrix literal can contain only constant values", literalValue.position)) - } - - val newValue = LiteralValue(arrayDt, arrayvalue = newArray, position = literalValue.position) - return super.process(newValue) + return moveArrayToHeap(literalValue) } return super.process(literalValue) } + private fun moveArrayToHeap(arraylit: LiteralValue): LiteralValue { + val array: Array = arraylit.arrayvalue!!.map { it.process(this) }.toTypedArray() + val allElementsAreConstant = array.fold(true) { c, expr-> c and (expr is LiteralValue)} + if(!allElementsAreConstant) { + addError(ExpressionError("array/matrix literal can contain only constant values", arraylit.position)) + return arraylit + } else { + val valuesInArray = array.map { it.constValue(namespace, heap)!!.asNumericValue!! } + val integerArray = valuesInArray.map{it.toInt()}.toIntArray() + val doubleArray = valuesInArray.map{it.toDouble()}.toDoubleArray() + val typesInArray: Set = array.mapNotNull { it.resultingDatatype(namespace, heap) }.toSet() + val arrayDt = + if(DataType.FLOAT in typesInArray) + DataType.ARRAY_F + else if(DataType.WORD in typesInArray) { + if(DataType.UWORD in typesInArray) + DataType.ARRAY_F + else + DataType.ARRAY_W + } else { + val maxValue = integerArray.max()!! + val minValue = integerArray.min()!! + if (minValue >= 0) { + // unsigned + if (maxValue <= 255) + DataType.ARRAY_UB + else + DataType.ARRAY_UW + } else { + // signed + if (maxValue <= 127) + DataType.ARRAY_B + else + DataType.ARRAY_W + } + } + + val heapId = when(arrayDt) { + DataType.ARRAY_UB, + DataType.ARRAY_B, + DataType.ARRAY_UW, + DataType.ARRAY_W, + DataType.MATRIX_UB, + DataType.MATRIX_B -> heap.add(arrayDt, integerArray) + DataType.ARRAY_F -> heap.add(arrayDt, doubleArray) + else -> throw CompilerException("invalid array type") + } + return LiteralValue(arrayDt, heapId = heapId, position = arraylit.position) + } + } + override fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression { if(arrayIndexedExpression.array.y!=null) { if(arrayIndexedExpression.array.size()!=null) {