diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index bd0a16c2b..f8accd4bc 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -4,18 +4,54 @@ sub start() { - byte[100] array - byte[4,5] mvar + byte[10] barray1 + byte[10] barray2 = 11 + byte[10] barray3 = [1,2,3,4,5,6,7,8,9,255] - A=AX[2] - A=AY[2] - A=XY[2] + word[10] warray1 + word[10] warray2 = 112233 + word[10] warray3 = [1,2,3,4,5,6,7,8,9, 65535] + + byte[4,5] mvar1 + byte[4,5] mvar2 = 22 + byte[2,3] mvar3 = [1,2,3,4,5,6] + + float[3] farray1 + float[3] farray2 = 33.44 + float[3] farray2b = 33 + float[3] farray3 = [1,2,3] + float[3] farray4 = [1,2,35566] + float[3] farray5 = [1,2.22334,3.1415] + + + byte i + word w + + for i in 0 to 2 { + _vm_write_num(farray5[i]) + _vm_write_char('\n') + } + + for w in [1,2,3777] { ;@todo loop over array literal + _vm_write_num(w) + _vm_write_char('\n') + } + + for i in barray3 { ; @todo loop over symbol + _vm_write_num(i) + _vm_write_char('\n') + } + + for i in "hello" { ; @todo loop over string + _vm_write_num(i) + _vm_write_char('\n') + } + + for w in "hello" { ; @todo loop over string + _vm_write_num(w) + _vm_write_char('\n') + } - A=array[98] - A=array[99] - A=mvar[13] - A=mvar[2,3] - A=mvar[Y,X] return } diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index d2da2cdc3..0cb2f050f 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -3,6 +3,7 @@ package prog8.ast import org.antlr.v4.runtime.ParserRuleContext import org.antlr.v4.runtime.tree.TerminalNode import prog8.compiler.HeapValues +import prog8.compiler.target.c64.Mflpt5 import prog8.compiler.target.c64.Petscii import prog8.compiler.unescape import prog8.functions.BuiltinFunctions @@ -26,6 +27,7 @@ enum class DataType { STR_PS, ARRAY, ARRAY_W, + ARRAY_F, MATRIX } @@ -60,7 +62,9 @@ enum class BranchCondition { POS } -val IterableDatatypes = setOf(DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS, DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX) +val IterableDatatypes = setOf( + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS, + DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX) class FatalAstException (override var message: String) : Exception(message) @@ -599,8 +603,9 @@ class VarDecl(val type: VarDeclType, else -> when (declaredDatatype) { DataType.BYTE -> DataType.ARRAY DataType.WORD -> DataType.ARRAY_W + DataType.FLOAT -> DataType.ARRAY_F else -> { - datatypeErrors.add(SyntaxError("array can only contain bytes or words", position)) + datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position)) DataType.BYTE } } @@ -620,7 +625,7 @@ class VarDecl(val type: VarDeclType, get() = when(datatype) { DataType.BYTE -> 1 DataType.WORD -> 2 - DataType.FLOAT -> 5 // MFLPT5 + DataType.FLOAT -> Mflpt5.MemorySize DataType.STR, DataType.STR_P, DataType.STR_S, @@ -636,6 +641,10 @@ class VarDecl(val type: VarDeclType, val aX = arrayspec?.x as? LiteralValue ?: throw ExpressionError("need constant value expression for arrayspec", position) 2*aX.asIntegerValue!! } + DataType.ARRAY_F -> { + val aX = arrayspec?.x as? LiteralValue ?: throw ExpressionError("need constant value expression for arrayspec", position) + Mflpt5.MemorySize*aX.asIntegerValue!! + } DataType.MATRIX -> { val aX = arrayspec?.x as? LiteralValue ?: throw ExpressionError("need constant value expression for arrayspec", position) val aY = arrayspec.y as? LiteralValue ?: throw ExpressionError("need constant value expression for arrayspec", position) @@ -833,10 +842,11 @@ class ArrayIndexedExpression(val identifier: IdentifierReference?, val target = identifier?.targetStatement(namespace) if (target is VarDecl) { return when (target.datatype) { + DataType.BYTE, DataType.WORD, DataType.FLOAT -> null DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.BYTE DataType.ARRAY, DataType.MATRIX -> DataType.BYTE DataType.ARRAY_W -> DataType.WORD - else -> null + DataType.ARRAY_F -> DataType.FLOAT } } throw FatalAstException("cannot get indexed element on $target") @@ -861,7 +871,7 @@ class LiteralValue(val type: DataType, val isString = type==DataType.STR || type==DataType.STR_P || type==DataType.STR_S || type==DataType.STR_PS val isNumeric = type==DataType.BYTE || type==DataType.WORD || type==DataType.FLOAT - val isArray = type==DataType.ARRAY || type==DataType.ARRAY_W || type==DataType.MATRIX + val isArray = type==DataType.ARRAY || type==DataType.ARRAY_W || type==DataType.ARRAY_F || type==DataType.MATRIX companion object { fun fromBoolean(bool: Boolean, position: Position) = @@ -908,7 +918,7 @@ class LiteralValue(val type: DataType, DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue") DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId") - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> + DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX -> if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId") } if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null) @@ -952,7 +962,7 @@ class LiteralValue(val type: DataType, if(heapId!=null) "str:#$heapId" else "str:$strvalue" } - DataType.ARRAY, DataType.ARRAY_W -> { + DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F -> { if(heapId!=null) "array:#$heapId" else "array:$arrayvalue" } diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index a08edcab3..227558d0f 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -2,6 +2,8 @@ package prog8.ast import prog8.compiler.CompilationOptions import prog8.compiler.HeapValues +import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE +import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE import prog8.functions.BuiltinFunctions import prog8.parser.ParsingFailedError @@ -111,7 +113,7 @@ class AstChecker(private val namespace: INameScope, if (iterableDt != DataType.WORD && iterableDt != DataType.BYTE && iterableDt != DataType.STR && iterableDt != DataType.STR_P && iterableDt != DataType.STR_S && iterableDt != DataType.STR_PS && - iterableDt !=DataType.ARRAY && iterableDt!=DataType.ARRAY_W && iterableDt!=DataType.MATRIX) + iterableDt !=DataType.ARRAY && iterableDt!=DataType.ARRAY_W && iterableDt!=DataType.ARRAY_F && iterableDt!=DataType.MATRIX) checkResult.add(ExpressionError("register pair can only loop over words", forLoop.position)) } } @@ -127,13 +129,16 @@ class AstChecker(private val namespace: INameScope, if(iterableDt!=DataType.BYTE && iterableDt!=DataType.ARRAY && iterableDt!=DataType.MATRIX && iterableDt != DataType.STR && iterableDt != DataType.STR_P && iterableDt != DataType.STR_S && iterableDt != DataType.STR_PS) - checkResult.add(ExpressionError("can only loop over bytes", forLoop.position)) + checkResult.add(ExpressionError("byte loop variable can only loop over bytes", forLoop.position)) } DataType.WORD -> { if(iterableDt!=DataType.BYTE && iterableDt!=DataType.WORD && - iterableDt !=DataType.ARRAY && iterableDt!=DataType.ARRAY_W && iterableDt!=DataType.MATRIX) - checkResult.add(ExpressionError("can only loop over bytes or words", forLoop.position)) + iterableDt !=DataType.ARRAY && iterableDt!=DataType.ARRAY_W && iterableDt!=DataType.MATRIX && + iterableDt != DataType.STR && iterableDt != DataType.STR_P && + iterableDt != DataType.STR_S && iterableDt != DataType.STR_PS) + checkResult.add(ExpressionError("word loop variable can only loop over bytes or words", forLoop.position)) } + // there's no support for a floating-point loop variable else -> checkResult.add(ExpressionError("loop variable must be byte or word type", forLoop.position)) } } @@ -331,7 +336,7 @@ class AstChecker(private val namespace: INameScope, litVal.parent = decl decl.value = litVal } - else -> err("var/const declaration needs a compile-time constant initializer value for this type") + else -> err("var/const declaration needs a compile-time constant initializer value for this type") // const fold should have provided it! } return super.process(decl) } @@ -438,11 +443,11 @@ class AstChecker(private val namespace: INameScope, if(lv.heapId==null) throw FatalAstException("string should have been moved to heap at ${lv.position}") } - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> { + DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX -> { if(lv.heapId==null) throw FatalAstException("array/matrix should have been moved to heap at ${lv.position}") } - else -> {} + DataType.BYTE, DataType.WORD, DataType.FLOAT -> {} } return lv } @@ -626,7 +631,7 @@ class AstChecker(private val namespace: INameScope, } return true } - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> { + DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX -> { // range and length check bytes val expectedSize = arrayspec!!.size() val rangeSize=range.size() @@ -721,17 +726,39 @@ class AstChecker(private val namespace: INameScope, return err("value '$number' out of range for unsigned word") } } + DataType.ARRAY_F -> { + // value may be either a single float, or a float array + if(value.type==DataType.ARRAY || value.type==DataType.ARRAY_W || value.type==DataType.ARRAY_F) { + val arraySize = value.arrayvalue?.size ?: heap.get(value.heapId!!).doubleArray!!.size + if(arrayspec!=null) { + // 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 { + val number = value.asNumericValue!!.toDouble() + if (number < FLOAT_MAX_NEGATIVE || number > FLOAT_MAX_POSITIVE) + return err("value '$number' out of range for mfplt5 floating point") + } + } DataType.MATRIX -> { // value can only be a single byte, or a byte array (which represents the matrix) if(value.type==DataType.ARRAY || value.type==DataType.MATRIX) { - val constX = arrayspec!!.x.constValue(namespace, heap) - val constY = arrayspec.y!!.constValue(namespace, heap) - if(constX?.asIntegerValue==null || constY?.asIntegerValue==null) - return err("matrix size specifiers must be constant integer values") - val matrix = heap.get(value.heapId!!).array!! - val expectedSize = constX.asIntegerValue * constY.asIntegerValue - if (matrix.size != expectedSize) - return err("initializer matrix size mismatch (expecting $expectedSize, got ${matrix.size} elements)") + if(arrayspec!=null) { + // arrayspec is not always known when checking + val constX = arrayspec.x.constValue(namespace, heap) + val constY = arrayspec.y!!.constValue(namespace, heap) + if (constX?.asIntegerValue == null || constY?.asIntegerValue == null) + return err("matrix size specifiers must be constant integer values") + val matrix = heap.get(value.heapId!!).array!! + val expectedSize = constX.asIntegerValue * constY.asIntegerValue + if (matrix.size != expectedSize) + return err("initializer matrix size mismatch (expecting $expectedSize, got ${matrix.size} elements)") + } } else { val number = value.bytevalue ?: return err("unsigned byte value expected") diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 76168a579..a0295035d 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -51,18 +51,19 @@ fun String.unescape(): String { class HeapValues { - data class HeapValue(val type: DataType, val str: String?, val array: IntArray?) { + data class HeapValue(val type: DataType, val str: String?, val array: IntArray?, val doubleArray: DoubleArray?) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as HeapValue - return type==other.type && str==other.str && Arrays.equals(array, other.array) + return type==other.type && str==other.str && Arrays.equals(array, other.array) && Arrays.equals(doubleArray, other.doubleArray) } override fun hashCode(): Int { var result = type.hashCode() result = 31 * result + (str?.hashCode() ?: 0) result = 31 * result + (array?.let { Arrays.hashCode(it) } ?: 0) + result = 31 * result + (doubleArray?.let { Arrays.hashCode(it) } ?: 0) return result } } @@ -74,7 +75,7 @@ class HeapValues { throw IllegalArgumentException("string length must be 1-255") // strings are 'interned' and shared if they're the same - val value = HeapValue(type, str, null) + val value = HeapValue(type, str, null, null) val existing = heap.indexOf(value) if(existing>=0) return existing @@ -84,7 +85,13 @@ class HeapValues { fun add(type: DataType, array: IntArray): Int { // arrays are never shared - heap.add(HeapValue(type, null, array)) + heap.add(HeapValue(type, null, array, null)) + return heap.size-1 + } + + fun add(type: DataType, darray: DoubleArray): Int { + // arrays are never shared + heap.add(HeapValue(type, null, null, darray)) return heap.size-1 } @@ -102,21 +109,15 @@ class HeapValues { } } - fun update(heapId: Int, array: IntArray) { - when(heap[heapId].type){ - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> { - if(heap[heapId].array!!.size != array.size) - throw IllegalArgumentException("heap array length mismatch") - heap[heapId] = heap[heapId].copy(array=array) - } - else-> throw IllegalArgumentException("heap data type mismatch") - } + fun update(heapId: Int, heapval: HeapValue) { + heap[heapId] = heapval } fun get(heapId: Int): HeapValue = heap[heapId] fun allStrings() = heap.asSequence().withIndex().filter { it.value.str!=null }.toList() fun allArrays() = heap.asSequence().withIndex().filter { it.value.array!=null }.toList() + fun allDoubleArrays() = heap.asSequence().withIndex().filter { it.value.doubleArray!=null }.toList() } @@ -146,7 +147,7 @@ class StackVmProgram(val name: String, val heap: HeapValues) { throw CompilerException("string should already be in the heap") Value(decl.datatype, litval.heapId) } - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> { + DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX -> { val litval = (decl.value as LiteralValue) if(litval.heapId==null) throw CompilerException("array/matrix should already be in the heap") @@ -458,7 +459,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, throw CompilerException("string should have been moved into heap ${lv.position}") stackvmProg.instr(Opcode.PUSH, Value(lv.type, lv.heapId)) } - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> { + DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX -> { if(lv.heapId==null) throw CompilerException("array/matrix should have been moved into heap ${lv.position}") stackvmProg.instr(Opcode.PUSH, Value(lv.type, lv.heapId)) @@ -688,7 +689,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, } } DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt") - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt") + DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt") // todo: maybe if you assign byte or word to array/matrix, clear it with that value? } } @@ -799,18 +800,21 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, } } } else { - val litVal = loop.iterable as? LiteralValue - val ident = loop.iterable as? IdentifierReference - when { - litVal?.strvalue != null -> { - TODO("loop over string $litVal") - } - ident!=null -> { - val symbol = ident.targetStatement(namespace) - TODO("loop over symbol: ${ident.nameInSource} -> $symbol") - } - else -> throw CompilerException("loopvar is something strange ${loop.iterable}") + val iterableValue: LiteralValue? + if(loop.iterable is LiteralValue) { + if (!loop.iterable.isIterable(namespace, heap)) + throw CompilerException("loop over something that isn't iterable ${loop.iterable}") + iterableValue = loop.iterable as LiteralValue + } else if(loop.iterable is IdentifierReference) { + val idRef = loop.iterable as IdentifierReference + iterableValue = ((idRef.targetStatement(namespace) as? VarDecl)?.value as? LiteralValue) + if(iterableValue!=null && !iterableValue.isIterable(namespace, heap)) + throw CompilerException("loop over something that isn't iterable ${loop.iterable}") + } else { + throw CompilerException("loopvar is something strange ${loop.iterable}") } + + TODO("LOOP OVER ITERABLE VALUE (array/matrix/string) $iterableValue") } } diff --git a/compiler/src/prog8/compiler/target/c64/Commodore64.kt b/compiler/src/prog8/compiler/target/c64/Commodore64.kt index 9f89d5c50..42d2cb839 100644 --- a/compiler/src/prog8/compiler/target/c64/Commodore64.kt +++ b/compiler/src/prog8/compiler/target/c64/Commodore64.kt @@ -54,6 +54,8 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) { data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) { companion object { + const val MemorySize = 5 + val zero = Mflpt5(0, 0,0,0,0) fun fromNumber(num: Number): Mflpt5 { // see https://en.wikipedia.org/wiki/Microsoft_Binary_Format diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index 6f34da233..fdbd14325 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -33,17 +33,17 @@ val BuiltinFunctions = mapOf( "sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sqrt) }, "rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toRadians) }, "deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toDegrees) }, - "avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX))), DataType.FLOAT, ::builtinAvg), + "avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX))), DataType.FLOAT, ::builtinAvg), "abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.FLOAT))), DataType.FLOAT, ::builtinAbs), "round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.FLOAT))), null) { a, p, n, h -> oneDoubleArgOutputInt(a, p, n, h, Math::round) }, // type depends on arg "floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.FLOAT))), null) { a, p, n, h -> oneDoubleArgOutputInt(a, p, n, h, Math::floor) }, // type depends on arg "ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.FLOAT))), null) { a, p, n, h -> oneDoubleArgOutputInt(a, p, n, h, Math::ceil) }, // type depends on arg - "max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX))), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.max()!! }}, // type depends on args - "min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX))), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.min()!! }}, // type depends on args - "sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX))), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.sum() }}, // type depends on args - "len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS, DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX))), null, ::builtinLen), // type depends on args - "any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX))), DataType.BYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.any { v -> v != 0.0} }}, - "all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX))), DataType.BYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.all { v -> v != 0.0} }}, + "max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX))), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.max()!! }}, // type depends on args + "min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX))), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.min()!! }}, // type depends on args + "sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX))), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.sum() }}, // type depends on args + "len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS, DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX))), null, ::builtinLen), // type depends on args + "any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX))), DataType.BYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.any { v -> v != 0.0} }}, + "all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", listOf(DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX))), DataType.BYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.all { v -> v != 0.0} }}, "lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.WORD))), DataType.BYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x and 255 }}, "msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.WORD))), DataType.BYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x ushr 8 and 255}}, "flt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", listOf(DataType.BYTE, DataType.WORD))), DataType.FLOAT, ::builtinFlt), @@ -87,7 +87,7 @@ fun builtinFunctionReturnType(function: String, args: List, namespa fun datatypeFromListArg(arglist: IExpression): DataType { if(arglist is LiteralValue) { - if(arglist.type==DataType.ARRAY || arglist.type==DataType.ARRAY_W || arglist.type==DataType.MATRIX) { + if(arglist.type==DataType.ARRAY || arglist.type==DataType.ARRAY_W || arglist.type==DataType.ARRAY_F || arglist.type==DataType.MATRIX) { val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace, heap)} if(dt.any { it!=DataType.BYTE && it!=DataType.WORD && it!=DataType.FLOAT}) { throw FatalAstException("fuction $function only accepts array of numeric values") @@ -104,6 +104,7 @@ fun builtinFunctionReturnType(function: String, args: List, namespa DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> dt DataType.ARRAY -> DataType.BYTE DataType.ARRAY_W -> DataType.WORD + DataType.ARRAY_F -> DataType.FLOAT DataType.MATRIX -> DataType.BYTE null -> throw FatalAstException("function requires one argument which is an array $function") } @@ -124,6 +125,7 @@ fun builtinFunctionReturnType(function: String, args: List, namespa DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.BYTE DataType.ARRAY -> DataType.BYTE DataType.ARRAY_W -> DataType.WORD + DataType.ARRAY_F -> DataType.FLOAT DataType.MATRIX -> DataType.BYTE } } @@ -142,6 +144,7 @@ fun builtinFunctionReturnType(function: String, args: List, namespa DataType.BYTE, DataType.WORD -> DataType.WORD DataType.FLOAT -> DataType.FLOAT DataType.ARRAY, DataType.ARRAY_W -> DataType.WORD + DataType.ARRAY_F -> DataType.FLOAT DataType.MATRIX -> DataType.WORD DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.WORD } @@ -306,15 +309,21 @@ private fun builtinLen(args: List, position: Position, namespace:IN ?: throw SyntaxError("len over weird argument ${args[0]}", position) } return when(argument.type) { - DataType.ARRAY, DataType.ARRAY_W -> { + DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> { val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).array!!.size numericLiteral(arraySize, args[0].position) } + DataType.ARRAY_F -> { + val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).doubleArray!!.size + numericLiteral(arraySize, args[0].position) + } DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { val str = argument.strvalue ?: heap.get(argument.heapId!!).str!! numericLiteral(str.length, args[0].position) } - else -> throw SyntaxError("len of weird argument ${args[0]}", position) + DataType.BYTE, + DataType.WORD, + DataType.FLOAT -> throw SyntaxError("len of weird argument ${args[0]}", position) } } diff --git a/compiler/src/prog8/optimizing/ConstantFolding.kt b/compiler/src/prog8/optimizing/ConstantFolding.kt index 5848930e5..134c085bd 100644 --- a/compiler/src/prog8/optimizing/ConstantFolding.kt +++ b/compiler/src/prog8/optimizing/ConstantFolding.kt @@ -2,6 +2,8 @@ package prog8.optimizing import prog8.ast.* import prog8.compiler.HeapValues +import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE +import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.Petscii @@ -52,12 +54,41 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> { val litval = decl.value as? LiteralValue val size = decl.arrayspec!!.size() - if (size != null) { + if(litval!=null && litval.isArray) { + // array initializer value is an array already, keep as-is + if(litval.heapId!=null) { + if (decl.datatype == DataType.MATRIX && litval.type != DataType.MATRIX) { + val array = heap.get(litval.heapId).copy(type = DataType.MATRIX) + heap.update(litval.heapId, array) + } + } + } 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 val fillArray = IntArray(size) { _ -> fillvalue } val heapId = heap.add(decl.datatype, fillArray) - val valType = if(decl.datatype==DataType.MATRIX) DataType.ARRAY else decl.datatype - decl.value = LiteralValue(valType, heapId = heapId, position = litval?.position ?: decl.position) + decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position) + } + } + DataType.ARRAY_F -> { + val litval = decl.value as? LiteralValue + val size = decl.arrayspec!!.size() + if(litval!=null && litval.isArray) { + // array initializer value is an array already, make sure to convert to floats + if(litval.heapId!=null) { + val array = heap.get(litval.heapId) + if (array.doubleArray == null) { + val doubleArray = array.array!!.map { it.toDouble() }.toDoubleArray() + heap.update(litval.heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray)) + 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.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) } } else -> return result @@ -66,21 +97,6 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV return result } - private fun createArrayInitValue(decl: VarDecl, intvalue: Int, position: Position) { - val size = decl.arrayspec!!.size() - if (size != null) { - val newArray = Array(size) { _ -> - if (decl.datatype == DataType.ARRAY) - LiteralValue(DataType.BYTE, bytevalue = intvalue.toShort(), position = position) - else - LiteralValue(DataType.WORD, wordvalue = intvalue, position = position) - } - decl.value = LiteralValue(decl.datatype, arrayvalue = newArray, position = position) - } else { - addError(SyntaxError("array size must be a constant integer value", position)) - } - } - /** * replace identifiers that refer to const value, with the value itself */ @@ -289,48 +305,54 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV } } else if(literalValue.arrayvalue!=null) { val newArray = literalValue.arrayvalue.map { it.process(this) }.toTypedArray() - // determine if the values are all bytes or that we need a word array instead var arrayDt = DataType.ARRAY - var allElementsAreConstant = true - for (expr in newArray) { - allElementsAreConstant = allElementsAreConstant and (expr is LiteralValue) - val valueDt = expr.resultingDatatype(namespace, heap) - if(valueDt==DataType.BYTE) - continue - else { - arrayDt = DataType.ARRAY_W - break - } - } + if(newArray.any { it.resultingDatatype(namespace, heap) == DataType.WORD }) + arrayDt = DataType.ARRAY_W + if(newArray.any { it.resultingDatatype(namespace, heap) == DataType.FLOAT }) + arrayDt = DataType.ARRAY_F // 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 array = newArray.map { - val litval = it as? LiteralValue - if(litval==null) { - addError(ExpressionError("array/matrix literal can contain only constant values", literalValue.position)) - return super.process(literalValue) - } - if(litval.bytevalue==null && litval.wordvalue==null) { - if(arrayDt==DataType.ARRAY) - addError(ExpressionError("byte array elements must all be integers 0..255", literalValue.position)) - else - addError(ExpressionError("word array elements must all be integers 0..65535", literalValue.position)) - return super.process(literalValue) - } - val integer = litval.asIntegerValue!! - if(arrayDt==DataType.ARRAY && integer !in 0..255) { + 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 || arrayDt==DataType.MATRIX) { + // all values should be bytes + 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) - } else if(arrayDt==DataType.ARRAY_W && integer !in 0..65535) { + } + 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_W) { + // 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) } - integer - }.toIntArray() - val heapId = heap.add(arrayDt, array) - val newValue = LiteralValue(arrayDt, heapId=heapId, position = literalValue.position) - return super.process(newValue) + 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)) } @@ -338,6 +360,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV val newValue = LiteralValue(arrayDt, arrayvalue = newArray, position = literalValue.position) return super.process(newValue) } + return super.process(literalValue) } @@ -349,9 +372,11 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV if(arrayIndexedExpression.identifier!=null) { val x = (arrayIndexedExpression.array.x as LiteralValue).asIntegerValue!! val y = (arrayIndexedExpression.array.y as LiteralValue).asIntegerValue!! - val variable = arrayIndexedExpression.identifier.targetStatement(namespace) as VarDecl - val index = x + y*(variable.arrayspec!!.x as LiteralValue).asIntegerValue!! - arrayIndexedExpression.array = ArraySpec(LiteralValue.optimalInteger(index, arrayIndexedExpression.array.position), null, arrayIndexedExpression.array.position) + val variable = arrayIndexedExpression.identifier.targetStatement(namespace) as? VarDecl + if(variable!=null) { + val index = x + y * (variable.arrayspec!!.x as LiteralValue).asIntegerValue!! + arrayIndexedExpression.array = ArraySpec(LiteralValue.optimalInteger(index, arrayIndexedExpression.array.position), null, arrayIndexedExpression.array.position) + } } } } diff --git a/compiler/src/prog8/stackvm/Program.kt b/compiler/src/prog8/stackvm/Program.kt index 0568638b4..3631999fe 100644 --- a/compiler/src/prog8/stackvm/Program.kt +++ b/compiler/src/prog8/stackvm/Program.kt @@ -67,7 +67,12 @@ class Program (val name: String, val intarray = numbers.map{number->number.trim().toInt()}.toIntArray() heap.add(it.second, intarray) } - else -> throw VmExecutionException("invalid heap value type $it.second") + DataType.ARRAY_F -> { + val numbers = it.third.substring(1, it.third.length-1).split(',') + val doublearray = numbers.map{number->number.trim().toDouble()}.toDoubleArray() + heap.add(it.second, doublearray) + } + DataType.BYTE, DataType.WORD, DataType.FLOAT -> throw VmExecutionException("invalid heap value type ${it.second}") } } } @@ -172,6 +177,7 @@ class Program (val name: String, } DataType.ARRAY, DataType.ARRAY_W, + DataType.ARRAY_F, DataType.MATRIX -> { if(!valueStr.startsWith("heap:")) throw VmExecutionException("invalid array/matrix value, should be a heap reference") @@ -281,6 +287,9 @@ class Program (val name: String, heap.allArrays().forEach { out.println("${it.index} ${it.value.type.toString().toLowerCase()} ${it.value.array!!.toList()}") } + heap.allDoubleArrays().forEach { + out.println("${it.index} ${it.value.type.toString().toLowerCase()} ${it.value.doubleArray!!.toList()}") + } out.println("%end_heap") out.println("%variables") // just flatten all block vars into one global list for now... diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index 932c1aee2..a84affd19 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -483,6 +483,7 @@ class StackVm(private var traceOutputFile: String?) { DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> print(heap.get(value.heapId).str) DataType.ARRAY, DataType.ARRAY_W -> print(heap.get(value.heapId).array!!.toList()) DataType.MATRIX -> print(heap.get(value.heapId).array!!.toList()) + DataType.ARRAY_F -> print(heap.get(value.heapId).doubleArray!!.toList()) } } Syscall.INPUT_STR -> { @@ -573,7 +574,8 @@ class StackVm(private var traceOutputFile: String?) { DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.BYTE DataType.ARRAY, DataType.MATRIX -> DataType.BYTE DataType.ARRAY_W -> DataType.WORD - else -> throw VmExecutionException("uniterable value $iterable") + DataType.ARRAY_F -> DataType.FLOAT + DataType.BYTE, DataType.WORD, DataType.FLOAT -> throw VmExecutionException("uniterable value $iterable") } if(value.str!=null) { val result = Petscii.encodePetscii(value.str.max().toString(), true)[0] @@ -590,7 +592,8 @@ class StackVm(private var traceOutputFile: String?) { DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.BYTE DataType.ARRAY, DataType.MATRIX -> DataType.BYTE DataType.ARRAY_W -> DataType.WORD - else -> throw VmExecutionException("uniterable value $iterable") + DataType.ARRAY_F -> DataType.FLOAT + DataType.BYTE, DataType.WORD, DataType.FLOAT -> throw VmExecutionException("uniterable value $iterable") } if(value.str!=null) { val result = Petscii.encodePetscii(value.str.min().toString(), true)[0] @@ -917,11 +920,17 @@ class StackVm(private var traceOutputFile: String?) { } else { // get indexed element from the array val array = heap.get(variable.heapId) - val result = array.array!![index] when(array.type) { - DataType.ARRAY, DataType.MATRIX -> evalstack.push(Value(DataType.BYTE, result)) - DataType.ARRAY_W -> evalstack.push(Value(DataType.WORD, result)) - else -> throw VmExecutionException("not a proper array/matrix var") + DataType.ARRAY, DataType.MATRIX -> evalstack.push(Value(DataType.BYTE, array.array!![index])) + DataType.ARRAY_W -> evalstack.push(Value(DataType.WORD, array.array!![index])) + DataType.ARRAY_F -> evalstack.push(Value(DataType.FLOAT, array.doubleArray!![index])) + DataType.BYTE, + DataType.WORD, + DataType.FLOAT, + DataType.STR, + DataType.STR_P, + DataType.STR_S, + DataType.STR_PS -> throw VmExecutionException("not a proper array/matrix var") } } } diff --git a/compiler/src/prog8/stackvm/Value.kt b/compiler/src/prog8/stackvm/Value.kt index 4c8ab46cc..4098e1194 100644 --- a/compiler/src/prog8/stackvm/Value.kt +++ b/compiler/src/prog8/stackvm/Value.kt @@ -74,7 +74,8 @@ class Value(val type: DataType, numericvalueOrHeapId: Number) { return false if(type==other.type) { return when (type) { - DataType.STR, DataType.STR_S, DataType.STR_P, DataType.STR_PS, DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> heapId==other.heapId + DataType.STR, DataType.STR_S, DataType.STR_P, DataType.STR_PS, + DataType.ARRAY, DataType.ARRAY_W, DataType.ARRAY_F, DataType.MATRIX -> heapId==other.heapId DataType.BYTE, DataType.WORD, DataType.FLOAT -> compareTo(other)==0 } } diff --git a/compiler/test/ValueOperationsTests.kt b/compiler/test/ValueOperationsTests.kt index 58a93b15d..f6996b2cd 100644 --- a/compiler/test/ValueOperationsTests.kt +++ b/compiler/test/ValueOperationsTests.kt @@ -86,9 +86,17 @@ class TestStackVmValue { assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR_P, 999))) assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 222))) + assertTrue(sameValueAndType(Value(DataType.ARRAY, 99), Value(DataType.ARRAY, 99))) + assertFalse(sameValueAndType(Value(DataType.ARRAY, 99), Value(DataType.MATRIX, 99))) + assertFalse(sameValueAndType(Value(DataType.ARRAY, 99), Value(DataType.ARRAY, 22))) + assertTrue(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.ARRAY_W, 999))) assertFalse(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.MATRIX, 999))) assertFalse(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.ARRAY_W, 222))) + + assertTrue(sameValueAndType(Value(DataType.ARRAY_F, 999), Value(DataType.ARRAY_F, 999))) + assertFalse(sameValueAndType(Value(DataType.ARRAY_F, 999), Value(DataType.ARRAY_W, 999))) + assertFalse(sameValueAndType(Value(DataType.ARRAY_F, 999), Value(DataType.ARRAY_F, 222))) } @Test diff --git a/docs/source/programming.rst b/docs/source/programming.rst index a672a19fc..792c993ad 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -180,7 +180,8 @@ Values will usually be part of an expression or assignment statement:: byte counter = 42 ; variable of size 8 bits, with initial value 42 -Array and Matrix (2-dimensional array) types are also supported like this:: +Array and Matrix (2-dimensional array) types are also supported. +Arrays can be made of bytes, words and floats. Matrixes can oly be made of bytes:: byte[4] array = [1, 2, 3, 4] ; initialize the array byte[99] array = 255 ; initialize array with all 255's [255, 255, 255, 255, ...] @@ -313,7 +314,8 @@ Loops ----- The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this. -The loop variable must be declared as byte or word earlier. Floating point iteration is not supported. +The loop variable must be declared as byte or word earlier. Floating point iteration is not supported, +if you want to loop over a floating-point array, use a loop with an integer index variable instead. The *while*-loop is used to repeat a piece of code while a certain condition is still true. The *repeat--until* loop is used to repeat a piece of code until a certain condition is true. diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 3fc9aff1e..cd98c60a7 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -235,6 +235,7 @@ type identifier type storage size example var declara stored in 5-byte cbm MFLPT format ``byte[x]`` unsigned byte array x bytes ``byte[4] myvar = [1, 2, 3, 4]`` ``word[x]`` unsigned word array 2*x bytes ``word[4] myvar = [1, 2, 3, 4]`` +``float[x]`` floating-point array 5*x bytes ``float[4] myvar = [1.1, 2.2, 3.3, 4.4]`` ``byte[x,y]`` unsigned byte matrix x*y bytes ``byte[40,25] myvar = @todo`` word-matrix not supported ``str`` string (petscii) varies ``str myvar = "hello."`` @@ -450,9 +451,9 @@ Loops for loop ^^^^^^^^ -The loop variable must be a register or a variable defined in the local scope. +The loop variable must be a register or a byte/word variable defined in the local scope. The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``, -array variables and strings). +array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported). You can use a single statement, or a statement block like in the example below::