diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index 259db62f0..405ed9fa3 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -15,11 +15,6 @@ import java.io.File internal class AstChecker(private val program: Program, private val compilerOptions: CompilationOptions) : IAstVisitor { private val checkResult: MutableList = mutableListOf() - private val heapIdSentinel: Int - init { - val stringSentinel = program.heap.allEntries().firstOrNull {it.value.str==""} - heapIdSentinel = stringSentinel?.key ?: program.heap.addString(DataType.STR, "") - } fun result(): List { return checkResult @@ -358,13 +353,13 @@ internal class AstChecker(private val program: Program, override fun visit(assignTarget: AssignTarget) { val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt() - if(memAddr!=null) { - if(memAddr<0 || memAddr>=65536) + if (memAddr != null) { + if (memAddr < 0 || memAddr >= 65536) checkResult.add(ExpressionError("address out of range", assignTarget.position)) } - val assignment = assignTarget.parent as Assignment - if(assignTarget.identifier!=null) { + val assignment = assignTarget.parent as IStatement + if (assignTarget.identifier != null) { val targetName = assignTarget.identifier.nameInSource val targetSymbol = program.namespace.lookup(targetName, assignment) when (targetSymbol) { @@ -377,7 +372,7 @@ internal class AstChecker(private val program: Program, return } else -> { - if(targetSymbol.type == VarDeclType.CONST) { + if (targetSymbol.type == VarDeclType.CONST) { checkResult.add(ExpressionError("cannot assign new value to a constant", assignment.position)) return } @@ -385,15 +380,17 @@ internal class AstChecker(private val program: Program, } } - if(assignment.aug_op!=null) - throw FatalAstException("augmented assignment should have been converted into normal assignment") + if (assignment is Assignment) { - val targetDatatype = assignTarget.inferType(program, assignment) - if(targetDatatype!=null) { - val constVal = assignment.value.constValue(program) - if(constVal!=null) { - checkValueTypeAndRange(targetDatatype, constVal) - // TODO what about arrays etc: + if (assignment.aug_op != null) + throw FatalAstException("augmented assignment should have been converted into normal assignment") + + val targetDatatype = assignTarget.inferType(program, assignment) + if (targetDatatype != null) { + val constVal = assignment.value.constValue(program) + if (constVal != null) { + checkValueTypeAndRange(targetDatatype, constVal) + // TODO what about arrays etc: // val targetVar = // if(target.identifier!=null) // program.namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl @@ -403,19 +400,18 @@ internal class AstChecker(private val program: Program, // checkValueTypeAndRange(targetDatatype, targetVar?.struct, // arrayspec ?: ArrayIndex(NumericLiteralValue.optimalInteger(-1, assignment.position), assignment.position), // constVal, program.heap) - } else { - val sourceDatatype: DataType? = assignment.value.inferType(program) - if(sourceDatatype==null) { - if (assignment.value is FunctionCall) { - val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace) - if(targetStmt!=null) - checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position)) + } else { + val sourceDatatype: DataType? = assignment.value.inferType(program) + if (sourceDatatype == null) { + if (assignment.value is FunctionCall) { + val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace) + if (targetStmt != null) + checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position)) + } else + checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position)) + } else { + checkAssignmentCompatible(targetDatatype, assignTarget, sourceDatatype, assignment.value, assignment.position) } - else - checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position)) - } - else { - checkAssignmentCompatible(targetDatatype, assignTarget, sourceDatatype, assignment.value, assignment.position) } } } diff --git a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt index ffd8bcfb3..f99c6e991 100644 --- a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt @@ -2,7 +2,6 @@ package prog8.ast.processing import prog8.ast.* import prog8.ast.base.* -import prog8.ast.base.autoHeapValuePrefix import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.functions.BuiltinFunctions @@ -12,7 +11,8 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo private val checkResult: MutableList = mutableListOf() private var blocks = mutableMapOf() - internal val anonymousVariablesFromHeap = mutableMapOf>() + internal val anonymousVariablesFromHeap = mutableMapOf>() // TODO + private val vardeclsToAdd = mutableMapOf>() internal fun result(): List { return checkResult @@ -23,8 +23,14 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo } override fun visit(module: Module) { + vardeclsToAdd.clear() blocks.clear() // blocks may be redefined within a different module super.visit(module) + // add any new vardecls to the various scopes + for((where, decls) in vardeclsToAdd) { + where.statements.addAll(0, decls) + decls.forEach { it.linkParents(where as Node) } + } } override fun visit(block: Block): IStatement { @@ -208,20 +214,13 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo } override fun visit(refLiteral: ReferenceLiteralValue): ReferenceLiteralValue { - if(refLiteral.heapId!=null && refLiteral.parent !is VarDecl) { - // a literal value that's not declared as a variable, which refers to something on the heap. - // we need to introduce an auto-generated variable for this to be able to refer to the value! - // (note: ususally, this has been taken care of already when the var was created) + if(refLiteral.parent !is VarDecl) { + // 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 val declaredType = if(refLiteral.isArray) ArrayElementTypes.getValue(refLiteral.type) else refLiteral.type - val variable = VarDecl(VarDeclType.VAR, - declaredType, - ZeropageWish.NOT_IN_ZEROPAGE, - null, - "$autoHeapValuePrefix${refLiteral.heapId}", - null, - refLiteral, - isArray = refLiteral.isArray, autogeneratedDontRemove = true, position = refLiteral.position) - anonymousVariablesFromHeap[variable.name] = Pair(refLiteral, variable) + val variable = VarDecl.createAuto(refLiteral) + addVarDecl(refLiteral.definingScope(), variable) + // TODO anonymousVariablesFromHeap[variable.name] = Pair(refLiteral, variable) } return super.visit(refLiteral) } @@ -243,4 +242,12 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo return super.visit(structDecl) } + private fun addVarDecl(scope: INameScope, variable: VarDecl) { + if(scope !in vardeclsToAdd) + vardeclsToAdd[scope] = mutableListOf() + val declList = vardeclsToAdd.getValue(scope) + if(declList.all{it.name!=variable.name}) + declList.add(variable) + } + } diff --git a/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt index 08ce82cb6..e6d257041 100644 --- a/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt +++ b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt @@ -2,7 +2,6 @@ package prog8.ast.processing import prog8.ast.* import prog8.ast.base.* -import prog8.ast.base.autoHeapValuePrefix import prog8.ast.expressions.* import prog8.ast.statements.* @@ -24,7 +23,6 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope override fun visit(module: Module) { vardeclsToAdd.clear() super.visit(module) - // add any new vardecls to the various scopes for((where, decls) in vardeclsToAdd) { where.statements.addAll(0, decls) @@ -96,17 +94,15 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope } else if(strvalue!=null) { if(strvalue.isString) { + // add a vardecl so that the autovar can be resolved in later lookups + val variable = VarDecl.createAuto(strvalue) + addVarDecl(strvalue.definingScope(), variable) // replace the argument with &autovar - val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}" - val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position) + val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position) val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position) - pointerExpr.scopedname = parent.makeScopedName(autoVarName) + pointerExpr.scopedname = parent.makeScopedName(variable.name) pointerExpr.linkParents(arglist[argparam.first.index].parent) arglist[argparam.first.index] = pointerExpr - // add a vardecl so that the autovar can be resolved in later lookups - val variable = VarDecl(VarDeclType.VAR, strvalue.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, strvalue, - isArray = false, autogeneratedDontRemove = false, position = strvalue.position) - addVarDecl(strvalue.definingScope(), variable) } } } diff --git a/compiler/src/prog8/ast/statements/AstStatements.kt b/compiler/src/prog8/ast/statements/AstStatements.kt index e46b80c14..e1107c292 100644 --- a/compiler/src/prog8/ast/statements/AstStatements.kt +++ b/compiler/src/prog8/ast/statements/AstStatements.kt @@ -162,6 +162,14 @@ class VarDecl(val type: VarDeclType, override val expensiveToInline get() = value!=null && value !is NumericLiteralValue + companion object { + fun createAuto(refLv: ReferenceLiteralValue): VarDecl { + val autoVarName = "$autoHeapValuePrefix${refLv.heapId}" + return VarDecl(VarDeclType.VAR, refLv.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, refLv, + isArray = false, autogeneratedDontRemove = true, position = refLv.position) + } + } + val datatypeErrors = mutableListOf() // don't crash at init time, report them in the AstChecker val datatype = if (!isArray) declaredDatatype diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 61c168f07..0cd24ba03 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -116,7 +116,7 @@ class HeapValues { fun get(heapId: Int): HeapValue { return heap[heapId] ?: - throw IllegalArgumentException("heapId not found in heap") + throw IllegalArgumentException("heapId $heapId not found in heap") } fun allEntries() = heap.entries @@ -746,7 +746,8 @@ internal class Compiler(private val program: Program) { val arg=args.single() when (arg.inferType(program)) { DataType.STR, DataType.STR_S -> createSyscall("${funcname}_str") - else -> throw CompilerException("wrong datatype for len()") + in ArrayDatatypes -> throw CompilerException("len() of an array type should have been const-folded") + else -> throw CompilerException("wrong datatype for len() $arg") } } "any", "all" -> { diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index ac8d78523..14772be8f 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -417,10 +417,10 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap RuntimeValue(decl.datatype, heapId = litval.heapId) } in ArrayDatatypes -> { - val litval = (decl.value as ReferenceLiteralValue) - if(litval.heapId==null) + val litval = (decl.value as? ReferenceLiteralValue) + if(litval!=null && litval.heapId==null) throw CompilerException("array should already be in the heap") - RuntimeValue(decl.datatype, heapId = litval.heapId) + RuntimeValue(decl.datatype, heapId = litval?.heapId ?: -999) } DataType.STRUCT -> { // struct variables have been flattened already diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index a99c7c5d0..95ce5a63c 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -7,6 +7,7 @@ import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.statements.VarDecl +import prog8.compiler.CompilerException import kotlin.math.* @@ -336,44 +337,40 @@ private fun builtinLen(args: List, position: Position, program: Pro // note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE. if(args.size!=1) throw SyntaxError("len requires one argument", position) - var argument = args[0].constValue(program) - if(argument==null) { - val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace) - val arraySize = directMemVar?.arraysize?.size() - if(arraySize != null) - return NumericLiteralValue.optimalInteger(arraySize, position) - if(args[0] !is IdentifierReference) - throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position) - val target = (args[0] as IdentifierReference).targetStatement(program.namespace) - val argValue = (target as? VarDecl)?.value - argument = argValue?.constValue(program) - ?: throw NotConstArgumentException() + val constArg = args[0].constValue(program) + if(constArg!=null) + throw SyntaxError("len of weird argument ${args[0]}", position) + + val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace) + var arraySize = directMemVar?.arraysize?.size() + if(arraySize != null) + return NumericLiteralValue.optimalInteger(arraySize, position) + if(args[0] !is IdentifierReference) + throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position) + val target = (args[0] as IdentifierReference).targetStatement(program.namespace) as VarDecl + + return when(target.datatype) { + DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { + arraySize = target.arraysize!!.size()!! + if(arraySize>256) + throw CompilerException("array length exceeds byte limit ${target.position}") + NumericLiteralValue.optimalInteger(arraySize, args[0].position) + } + DataType.ARRAY_F -> { + arraySize = target.arraysize!!.size()!! + if(arraySize>256) + throw CompilerException("array length exceeds byte limit ${target.position}") + NumericLiteralValue.optimalInteger(arraySize, args[0].position) + } + in StringDatatypes -> { + val refLv = target.value as ReferenceLiteralValue + if(refLv.str!!.length>255) + throw CompilerException("string length exceeds byte limit ${refLv.position}") + NumericLiteralValue.optimalInteger(refLv.str.length, args[0].position) + } + in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position) + else -> throw CompilerException("weird datatype") } - - TODO("collection functions over iterables (array, string) $argument") - -// return when(argument.type) { -// DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { -// val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize -// if(arraySize>256) -// throw CompilerException("array length exceeds byte limit ${argument.position}") -// LiteralValue.optimalInteger(arraySize, args[0].position) -// } -// DataType.ARRAY_F -> { -// val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize -// if(arraySize>256) -// throw CompilerException("array length exceeds byte limit ${argument.position}") -// LiteralValue.optimalInteger(arraySize, args[0].position) -// } -// in StringDatatypes -> { -// val str = argument.strvalue!! -// if(str.length>255) -// throw CompilerException("string length exceeds byte limit ${argument.position}") -// LiteralValue.optimalInteger(str.length, args[0].position) -// } -// in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position) -// else -> throw CompilerException("weird datatype") -// } } diff --git a/compiler/src/prog8/optimizer/ConstantFolding.kt b/compiler/src/prog8/optimizer/ConstantFolding.kt index 83d5725ad..db39cd57e 100644 --- a/compiler/src/prog8/optimizer/ConstantFolding.kt +++ b/compiler/src/prog8/optimizer/ConstantFolding.kt @@ -618,20 +618,19 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor { throw FatalAstException("missing array value") } - val array = litval.array!! - val typesInArray = array.mapNotNull { it.inferType(program) }.toSet() + val typesInArray = litval.array.mapNotNull { it.inferType(program) }.toSet() val arrayDt = when { - array.any { it is AddressOf } -> DataType.ARRAY_UW + litval.array.any { it is AddressOf } -> DataType.ARRAY_UW DataType.FLOAT in typesInArray -> DataType.ARRAY_F DataType.WORD in typesInArray -> DataType.ARRAY_W else -> { - val allElementsAreConstantOrAddressOf = array.fold(true) { c, expr-> c and (expr is NumericLiteralValue|| expr is AddressOf)} + val allElementsAreConstantOrAddressOf = litval.array.fold(true) { c, expr-> c and (expr is NumericLiteralValue|| expr is AddressOf)} if(!allElementsAreConstantOrAddressOf) { addError(ExpressionError("array literal can only consist of constant primitive numerical values or memory pointers", litval.position)) return litval } else { - val integerArray = array.map { it.constValue(program)!!.number.toInt() } + val integerArray = litval.array.map { it.constValue(program)!!.number.toInt() } val maxValue = integerArray.max()!! val minValue = integerArray.min()!! if (minValue >= 0) { diff --git a/compiler/src/prog8/vm/stackvm/Program.kt b/compiler/src/prog8/vm/stackvm/Program.kt index ea99db949..7432d133b 100644 --- a/compiler/src/prog8/vm/stackvm/Program.kt +++ b/compiler/src/prog8/vm/stackvm/Program.kt @@ -226,18 +226,18 @@ class Program (val name: String, if(valueStr[0] !='"' && ':' !in valueStr) throw VmExecutionException("missing value type character") val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) { - DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).toShort(16)) - DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).toShort(16)) - DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).toInt(16)) - DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).toInt(16)) - DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).toDouble()) + DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).substringBefore(' ').toShort(16))// TODO process ZP and struct info? + DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).substringBefore(' ').toShort(16))// TODO process ZP and struct info? + DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).substringBefore(' ').toInt(16))// TODO process ZP and struct info? + DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).substringBefore(' ').toInt(16))// TODO process ZP and struct info? + DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).substringBefore(' ').toDouble())// TODO process ZP and struct info? in StringDatatypes -> { if(valueStr.startsWith('"') && valueStr.endsWith('"')) throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap") else if(!valueStr.startsWith("heap:")) throw VmExecutionException("invalid string value, should be a heap reference") else { - val heapId = valueStr.substring(5).toInt() + val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info? RuntimeValue(type, heapId = heapId) } } @@ -245,7 +245,7 @@ class Program (val name: String, if(!valueStr.startsWith("heap:")) throw VmExecutionException("invalid array value, should be a heap reference") else { - val heapId = valueStr.substring(5).toInt() + val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info? RuntimeValue(type, heapId = heapId) } } diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 136c80a39..80466d854 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -215,7 +215,7 @@ Variable declarations ^^^^^^^^^^^^^^^^^^^^^ Variables should be declared with their exact type and size so the compiler can allocate storage -for them. You must give them an initial value as well. That value can be a simple literal value, +for them. You can give them an initial value as well. That value can be a simple literal value, or an expression. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it when selecting variables to be put into zeropage. The syntax is:: diff --git a/examples/test.p8 b/examples/test.p8 index 56bd56ed6..7f5cec16f 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,41 +3,17 @@ ~ main { - struct Color { - uword red - ubyte green - ubyte blue - } - - str naam = "irmen" - word[] array = [1,2,3,4] - uword uw = $ab12 - Color rgb = [255,128,0] - Color rgb2 = [111,222,33] - - ubyte @zp zpvar=99 - sub start() { - uword fake_address - - fake_address = &naam - c64scr.print_uwhex(1, fake_address) - c64scr.print(", ") - - fake_address = &array - c64scr.print_uwhex(1, fake_address) - c64scr.print(", ") - - fake_address = &rgb - c64scr.print_uwhex(1, fake_address) - c64scr.print("\n") - - ; @todo only works once reference types are actually references: - ;str name2 = naam ; @todo name2 points to same str as naam - ;str name2 = fake_address ; @todo fake_address hopefully points to a str - ;Color colz = fake_address ; @todo fake_address hopefully points to a Color + str naam = "irmen" + ubyte length = len(naam) + c64scr.print(naam) + c64scr.print("irmen") + c64scr.print("irmen2") + c64scr.print("irmen2") + ubyte length2 = len("irmen") ; @todo same string as 'naam' + ubyte length3 = len("zxfdsfsf") ; @todo new string return }