From 6bd99d63b4f757c63f4bba2562278e58dcc2fb91 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 14 Mar 2020 23:47:26 +0100 Subject: [PATCH] cleanup of error reporting --- compiler/src/prog8/CompilerMain.kt | 3 +- compiler/src/prog8/ast/base/ErrorReporting.kt | 44 ++- compiler/src/prog8/ast/base/Extensions.kt | 24 +- .../prog8/ast/expressions/AstExpressions.kt | 2 +- .../prog8/ast/expressions/InferredTypes.kt | 2 +- .../processing/AnonymousScopeVarsCleanup.kt | 6 +- .../src/prog8/ast/processing/AstChecker.kt | 282 +++++++++--------- .../ast/processing/AstIdentifiersChecker.kt | 32 +- .../ast/processing/AstRecursionChecker.kt | 6 +- .../ast/processing/IAstModifyingVisitor.kt | 8 +- .../ImportedModuleDirectiveRemover.kt | 6 +- .../prog8/ast/processing/TypecastsAdder.kt | 6 +- compiler/src/prog8/compiler/Main.kt | 64 ++-- compiler/src/prog8/compiler/Zeropage.kt | 6 +- .../compiler/target/c64/codegen/AsmGen.kt | 8 +- .../target/c64/codegen/AssignmentAsmGen.kt | 2 +- .../c64/codegen/BuiltinFunctionsAsmGen.kt | 2 +- .../target/c64/codegen/ExpressionsAsmGen.kt | 2 +- .../target/c64/codegen/ForLoopsAsmGen.kt | 4 +- .../target/c64/codegen/FunctionCallAsmGen.kt | 2 +- .../target/c64/codegen/PostIncrDecrAsmGen.kt | 2 +- compiler/src/prog8/optimizer/Extensions.kt | 6 +- .../src/prog8/optimizer/StatementOptimizer.kt | 27 +- compiler/src/prog8/parser/ModuleParsing.kt | 15 +- compiler/src/prog8/vm/RuntimeValue.kt | 2 +- compiler/src/prog8/vm/astvm/AstVm.kt | 2 +- compiler/src/prog8/vm/astvm/ScreenDialog.kt | 2 +- compiler/test/UnitTests.kt | 60 ++-- 28 files changed, 319 insertions(+), 308 deletions(-) diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index f20604be7..f5806658d 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -2,7 +2,8 @@ package prog8 import kotlinx.cli.* import prog8.ast.base.AstException -import prog8.compiler.* +import prog8.compiler.CompilationResult +import prog8.compiler.compileProgram import prog8.compiler.target.CompilationTarget import prog8.compiler.target.c64.C64MachineDefinition import prog8.compiler.target.c64.Petscii diff --git a/compiler/src/prog8/ast/base/ErrorReporting.kt b/compiler/src/prog8/ast/base/ErrorReporting.kt index da8e44134..e760f0408 100644 --- a/compiler/src/prog8/ast/base/ErrorReporting.kt +++ b/compiler/src/prog8/ast/base/ErrorReporting.kt @@ -1,17 +1,39 @@ package prog8.ast.base +import prog8.parser.ParsingFailedError -enum class MessageSeverity { - WARNING, - ERROR + +class ErrorReporter { + private enum class MessageSeverity { + WARNING, + ERROR + } + private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position?) + + private val messages = mutableListOf() + private val alreadyReportedMessages = mutableSetOf() + + fun err(msg: String, position: Position?) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position)) + fun warn(msg: String, position: Position?) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position)) + + fun handle() { + messages.forEach { + when(it.severity) { + MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red + MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow + } + val msg = "${it.position} ${it.severity} ${it.message}".trim() + if(msg !in alreadyReportedMessages) { + System.err.println(msg) + alreadyReportedMessages.add(msg) + } + System.err.print("\u001b[0m") // reset color + } + val numErrors = messages.count { it.severity==MessageSeverity.ERROR } + messages.clear() + if(numErrors>0) + throw ParsingFailedError("There are $numErrors errors.") + } } -class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position?) - - -fun printWarning(message: String, position: Position? = null) { - print("\u001b[93m") // bright yellow - val msg = "$position Warning: $message".trim() - print("\n\u001b[0m") // normal -} diff --git a/compiler/src/prog8/ast/base/Extensions.kt b/compiler/src/prog8/ast/base/Extensions.kt index 9103faf64..7eb32ddf1 100644 --- a/compiler/src/prog8/ast/base/Extensions.kt +++ b/compiler/src/prog8/ast/base/Extensions.kt @@ -17,14 +17,14 @@ internal fun Program.removeNopsFlattenAnonScopes() { } -internal fun Program.checkValid(compilerOptions: CompilationOptions, compilerMessages: MutableList) { - val checker = AstChecker(this, compilerOptions, compilerMessages) +internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) { + val checker = AstChecker(this, compilerOptions, errors) checker.visit(this) } -internal fun Program.anonscopeVarsCleanup(compilerMessages: MutableList) { - val mover = AnonymousScopeVarsCleanup(this, compilerMessages) +internal fun Program.anonscopeVarsCleanup(errors: ErrorReporter) { + val mover = AnonymousScopeVarsCleanup(errors) mover.visit(this) } @@ -37,25 +37,25 @@ internal fun Program.reorderStatements() { checker.visit(this) } -internal fun Program.addTypecasts(compilerMessages: MutableList) { - val caster = TypecastsAdder(this, compilerMessages) +internal fun Program.addTypecasts(errors: ErrorReporter) { + val caster = TypecastsAdder(this, errors) caster.visit(this) } -internal fun Module.checkImportedValid(compilerMessages: MutableList) { - val checker = ImportedModuleDirectiveRemover(compilerMessages) +internal fun Module.checkImportedValid(errors: ErrorReporter) { + val checker = ImportedModuleDirectiveRemover(errors) checker.visit(this) } -internal fun Program.checkRecursion(compilerMessages: MutableList) { - val checker = AstRecursionChecker(namespace, compilerMessages) +internal fun Program.checkRecursion(errors: ErrorReporter) { + val checker = AstRecursionChecker(namespace, errors) checker.visit(this) checker.processMessages(name) } -internal fun Program.checkIdentifiers(compilerMessages: MutableList) { - val checker = AstIdentifiersChecker(this, compilerMessages) +internal fun Program.checkIdentifiers(errors: ErrorReporter) { + val checker = AstIdentifiersChecker(this, errors) checker.visit(this) if(modules.map {it.name}.toSet().size != modules.size) { diff --git a/compiler/src/prog8/ast/expressions/AstExpressions.kt b/compiler/src/prog8/ast/expressions/AstExpressions.kt index 882b75fa3..050c60ba7 100644 --- a/compiler/src/prog8/ast/expressions/AstExpressions.kt +++ b/compiler/src/prog8/ast/expressions/AstExpressions.kt @@ -10,7 +10,7 @@ import prog8.compiler.target.CompilationTarget import prog8.functions.BuiltinFunctions import prog8.functions.NotConstArgumentException import prog8.functions.builtinFunctionReturnType -import java.util.Objects +import java.util.* import kotlin.math.abs diff --git a/compiler/src/prog8/ast/expressions/InferredTypes.kt b/compiler/src/prog8/ast/expressions/InferredTypes.kt index a11b4ff48..90c7ce034 100644 --- a/compiler/src/prog8/ast/expressions/InferredTypes.kt +++ b/compiler/src/prog8/ast/expressions/InferredTypes.kt @@ -1,7 +1,7 @@ package prog8.ast.expressions -import java.util.Objects import prog8.ast.base.DataType +import java.util.* object InferredTypes { diff --git a/compiler/src/prog8/ast/processing/AnonymousScopeVarsCleanup.kt b/compiler/src/prog8/ast/processing/AnonymousScopeVarsCleanup.kt index d3902165d..d04926bac 100644 --- a/compiler/src/prog8/ast/processing/AnonymousScopeVarsCleanup.kt +++ b/compiler/src/prog8/ast/processing/AnonymousScopeVarsCleanup.kt @@ -1,12 +1,12 @@ package prog8.ast.processing import prog8.ast.Program -import prog8.ast.base.CompilerMessage +import prog8.ast.base.ErrorReporter import prog8.ast.statements.AnonymousScope import prog8.ast.statements.Statement import prog8.ast.statements.VarDecl -class AnonymousScopeVarsCleanup(val program: Program, compilerMessages: MutableList): IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { +class AnonymousScopeVarsCleanup(private val errors: ErrorReporter): IAstModifyingVisitor { private val varsToMove: MutableMap> = mutableMapOf() override fun visit(program: Program) { @@ -19,7 +19,7 @@ class AnonymousScopeVarsCleanup(val program: Program, compilerMessages: MutableL decls.forEach { val existing = existingVariables[it.name] if (existing!=null) { - err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position) + errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position) conflicts = true } } diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index d1818d2b3..1dca4ba5e 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -13,22 +13,22 @@ import java.io.File internal class AstChecker(private val program: Program, private val compilerOptions: CompilationOptions, - compilerMessages: MutableList) : IAstVisitor, ErrorReportingVisitor(compilerMessages) { + private val errors: ErrorReporter) : IAstVisitor { override fun visit(program: Program) { assert(program === this.program) // there must be a single 'main' block with a 'start' subroutine for the program entry point. val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block } if(mainBlocks.size>1) - err("more than one 'main' block", mainBlocks[0].position) + errors.err("more than one 'main' block", mainBlocks[0].position) for(mainBlock in mainBlocks) { val startSub = mainBlock.subScopes()["start"] as? Subroutine if (startSub == null) { - err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position) + errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position) } else { if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty()) - err("program entrypoint subroutine can't have parameters and/or return values", startSub.position) + errors.err("program entrypoint subroutine can't have parameters and/or return values", startSub.position) } // the main module cannot contain 'regular' statements (they will never be executed!) @@ -45,7 +45,7 @@ internal class AstChecker(private val program: Program, else -> false } if (!ok) { - err("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position) + errors.err("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position) break } } @@ -55,12 +55,12 @@ internal class AstChecker(private val program: Program, // which will be used as the 60hz irq routine in the vm if it's present val irqBlocks = program.modules.flatMap { it.statements }.filter { it is Block && it.name=="irq" }.map { it as Block } if(irqBlocks.size>1) - err("more than one 'irq' block", irqBlocks[0].position) + errors.err("more than one 'irq' block", irqBlocks[0].position) for(irqBlock in irqBlocks) { val irqSub = irqBlock.subScopes()["irq"] as? Subroutine if (irqSub != null) { if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty()) - err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position) + errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position) } } @@ -73,7 +73,7 @@ internal class AstChecker(private val program: Program, directives.filter { it.value.size > 1 }.forEach{ entry -> when(entry.key) { "%output", "%launcher", "%zeropage", "%address" -> - entry.value.forEach { err("directive can just occur once", it.position) } + entry.value.forEach { errors.err("directive can just occur once", it.position) } } } } @@ -85,18 +85,18 @@ internal class AstChecker(private val program: Program, } if(expectedReturnValues.isEmpty() && returnStmt.value!=null) { - err("invalid number of return values", returnStmt.position) + errors.err("invalid number of return values", returnStmt.position) } if(expectedReturnValues.isNotEmpty() && returnStmt.value==null) { - err("invalid number of return values", returnStmt.position) + errors.err("invalid number of return values", returnStmt.position) } if(expectedReturnValues.size==1 && returnStmt.value!=null) { val valueDt = returnStmt.value!!.inferType(program) if(!valueDt.isKnown) { - err("return value type mismatch", returnStmt.value!!.position) + errors.err("return value type mismatch", returnStmt.value!!.position) } else { if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.STRUCT)) - err("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position) + errors.err("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position) } } super.visit(returnStmt) @@ -104,53 +104,53 @@ internal class AstChecker(private val program: Program, override fun visit(ifStatement: IfStatement) { if(ifStatement.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes) - err("condition value should be an integer type", ifStatement.condition.position) + errors.err("condition value should be an integer type", ifStatement.condition.position) super.visit(ifStatement) } override fun visit(forLoop: ForLoop) { if(forLoop.body.containsNoCodeNorVars()) - warn("for loop body is empty", forLoop.position) + errors.warn("for loop body is empty", forLoop.position) val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE) if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) { - err("can only loop over an iterable type", forLoop.position) + errors.err("can only loop over an iterable type", forLoop.position) } else { if (forLoop.loopRegister != null) { // loop register if (iterableDt != DataType.ARRAY_UB && iterableDt != DataType.ARRAY_B && iterableDt != DataType.STR) - err("register can only loop over bytes", forLoop.position) + errors.err("register can only loop over bytes", forLoop.position) if(forLoop.loopRegister!=Register.A) - err("it's only possible to use A as a loop register", forLoop.position) + errors.err("it's only possible to use A as a loop register", forLoop.position) } else { // loop variable val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace) if(loopvar==null || loopvar.type== VarDeclType.CONST) { - err("for loop requires a variable to loop with", forLoop.position) + errors.err("for loop requires a variable to loop with", forLoop.position) } else { when (loopvar.datatype) { DataType.UBYTE -> { if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR) - err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position) + errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position) } DataType.UWORD -> { if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR && iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW) - err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position) + errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position) } DataType.BYTE -> { if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B) - err("byte loop variable can only loop over bytes", forLoop.position) + errors.err("byte loop variable can only loop over bytes", forLoop.position) } DataType.WORD -> { if(iterableDt!= DataType.BYTE && iterableDt!= DataType.WORD && iterableDt != DataType.ARRAY_B && iterableDt!= DataType.ARRAY_W) - err("word loop variable can only loop over bytes or words", forLoop.position) + errors.err("word loop variable can only loop over bytes or words", forLoop.position) } DataType.FLOAT -> { - err("for loop only supports integers", forLoop.position) + errors.err("for loop only supports integers", forLoop.position) } - else -> err("loop variable must be numeric type", forLoop.position) + else -> errors.err("loop variable must be numeric type", forLoop.position) } } } @@ -164,18 +164,18 @@ internal class AstChecker(private val program: Program, val targetStatement = checkFunctionOrLabelExists(jump.identifier, jump) if(targetStatement!=null) { if(targetStatement is BuiltinFunctionStatementPlaceholder) - err("can't jump to a builtin function", jump.position) + errors.err("can't jump to a builtin function", jump.position) } } if(jump.address!=null && (jump.address < 0 || jump.address > 65535)) - err("jump address must be valid integer 0..\$ffff", jump.position) + errors.err("jump address must be valid integer 0..\$ffff", jump.position) super.visit(jump) } override fun visit(block: Block) { if(block.address!=null && (block.address<0 || block.address>65535)) { - err("block memory address must be valid integer 0..\$ffff", block.position) + errors.err("block memory address must be valid integer 0..\$ffff", block.position) } super.visit(block) @@ -184,15 +184,13 @@ internal class AstChecker(private val program: Program, override fun visit(label: Label) { // scope check if(label.parent !is Block && label.parent !is Subroutine && label.parent !is AnonymousScope) { - err("Labels can only be defined in the scope of a block, a loop body, or within another subroutine", label.position) + errors.err("Labels can only be defined in the scope of a block, a loop body, or within another subroutine", label.position) } super.visit(label) } override fun visit(subroutine: Subroutine) { - fun err(msg: String) { - err(msg, subroutine.position) - } + fun err(msg: String) = errors.err(msg, subroutine.position) if(subroutine.name in BuiltinFunctions) err("cannot redefine a built-in function") @@ -328,17 +326,17 @@ internal class AstChecker(private val program: Program, override fun visit(repeatLoop: RepeatLoop) { if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) - warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position) + errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position) if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes) - err("condition value should be an integer type", repeatLoop.untilCondition.position) + errors.err("condition value should be an integer type", repeatLoop.untilCondition.position) super.visit(repeatLoop) } override fun visit(whileLoop: WhileLoop) { if(whileLoop.condition.referencesIdentifiers("A", "X", "Y")) - warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position) + errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position) if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes) - err("condition value should be an integer type", whileLoop.condition.position) + errors.err("condition value should be an integer type", whileLoop.condition.position) super.visit(whileLoop) } @@ -348,11 +346,11 @@ internal class AstChecker(private val program: Program, val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace) if (stmt is Subroutine && stmt.isAsmSubroutine) { if(stmt.returntypes.size>1) - err("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position) + errors.err("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position) else { val idt = assignment.target.inferType(program, assignment) if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) { - err("return type mismatch", assignment.value.position) + errors.err("return type mismatch", assignment.value.position) } } } @@ -365,7 +363,7 @@ internal class AstChecker(private val program: Program, val targetVar = targetIdent.targetVarDecl(program.namespace) if(sourceVar?.struct!=null && targetVar?.struct!=null) { if(sourceVar.struct!==targetVar.struct) - err("assignment of different struct types", assignment.position) + errors.err("assignment of different struct types", assignment.position) } } @@ -378,7 +376,7 @@ internal class AstChecker(private val program: Program, val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt() if (memAddr != null) { if (memAddr < 0 || memAddr >= 65536) - err("address out of range", assignTarget.position) + errors.err("address out of range", assignTarget.position) } val assignment = assignTarget.parent as Statement @@ -388,16 +386,16 @@ internal class AstChecker(private val program: Program, val targetSymbol = program.namespace.lookup(targetName, assignment) when (targetSymbol) { null -> { - err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position) + errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position) return } !is VarDecl -> { - err("assignment LHS must be register or variable", assignment.position) + errors.err("assignment LHS must be register or variable", assignment.position) return } else -> { if (targetSymbol.type == VarDeclType.CONST) { - err("cannot assign new value to a constant", assignment.position) + errors.err("cannot assign new value to a constant", assignment.position) return } } @@ -405,7 +403,7 @@ internal class AstChecker(private val program: Program, } val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR) if(targetDt in IterableDatatypes) - err("cannot assign to a string or array type", assignTarget.position) + errors.err("cannot assign to a string or array type", assignTarget.position) if (assignment is Assignment) { @@ -423,9 +421,9 @@ internal class AstChecker(private val program: Program, if (assignment.value is FunctionCall) { val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace) if (targetStmt != null) - err("function call doesn't return a suitable value to use in assignment", assignment.value.position) + errors.err("function call doesn't return a suitable value to use in assignment", assignment.value.position) } else - err("assignment value is invalid or has no proper datatype", assignment.value.position) + errors.err("assignment value is invalid or has no proper datatype", assignment.value.position) } else { checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget, sourceDatatype.typeOrElse(DataType.BYTE), assignment.value, assignment.position) @@ -438,10 +436,10 @@ internal class AstChecker(private val program: Program, override fun visit(addressOf: AddressOf) { val variable=addressOf.identifier.targetVarDecl(program.namespace) if(variable==null) - err("pointer-of operand must be the name of a heap variable", addressOf.position) + errors.err("pointer-of operand must be the name of a heap variable", addressOf.position) else { if(variable.datatype !in ArrayDatatypes && variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT) - err("invalid pointer-of operand type", addressOf.position) + errors.err("invalid pointer-of operand type", addressOf.position) } super.visit(addressOf) } @@ -464,19 +462,19 @@ internal class AstChecker(private val program: Program, // FLOATS if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY) { - err("floating point used, but that is not enabled via options", decl.position) + err("floating point used, but that is not enabled via options") } // ARRAY without size specifier MUST have an iterable initializer value if(decl.isArray && decl.arraysize==null) { if(decl.type== VarDeclType.MEMORY) - err("memory mapped array must have a size specification", decl.position) + err("memory mapped array must have a size specification") if(decl.value==null) { - err("array variable is missing a size specification or an initialization value", decl.position) + err("array variable is missing a size specification or an initialization value") return } if(decl.value is NumericLiteralValue) { - err("unsized array declaration cannot use a single literal initialization value", decl.position) + err("unsized array declaration cannot use a single literal initialization value") return } if(decl.value is RangeExpr) @@ -542,19 +540,19 @@ internal class AstChecker(private val program: Program, val struct = decl.struct!! val structLv = decl.value as StructLiteralValue if(struct.numberOfElements != structLv.values.size) { - err("struct value has incorrect number of elements", structLv.position) + errors.err("struct value has incorrect number of elements", structLv.position) return } for(value in structLv.values.zip(struct.statements)) { val memberdecl = value.second as VarDecl val constValue = value.first.constValue(program) if(constValue==null) { - err("struct literal value for field '${memberdecl.name}' should consist of compile-time constants", value.first.position) + errors.err("struct literal value for field '${memberdecl.name}' should consist of compile-time constants", value.first.position) return } val memberDt = memberdecl.datatype if(!checkValueTypeAndRange(memberDt, constValue)) { - err("struct member value's type is not compatible with member field '${memberdecl.name}'", value.first.position) + errors.err("struct member value's type is not compatible with member field '${memberdecl.name}'", value.first.position) return } } @@ -589,7 +587,7 @@ internal class AstChecker(private val program: Program, } else { val value = decl.value as NumericLiteralValue if (value.type !in IntegerDatatypes || value.number.toInt() < 0 || value.number.toInt() > 65535) { - err("memory address must be valid integer 0..\$ffff") + err("memory address must be valid integer 0..\$ffff", decl.value?.position) } } } @@ -600,21 +598,24 @@ internal class AstChecker(private val program: Program, override fun visit(directive: Directive) { fun err(msg: String) { - err(msg, directive.position) + errors.err(msg, directive.position) } when(directive.directive) { "%output" -> { - if(directive.parent !is Module) err("this directive may only occur at module level") + if(directive.parent !is Module) + err("this directive may only occur at module level") if(directive.args.size!=1 || directive.args[0].name != "raw" && directive.args[0].name != "prg") err("invalid output directive type, expected raw or prg") } "%launcher" -> { - if(directive.parent !is Module) err("this directive may only occur at module level") + if(directive.parent !is Module) + err("this directive may only occur at module level") if(directive.args.size!=1 || directive.args[0].name != "basic" && directive.args[0].name != "none") err("invalid launcher directive type, expected basic or none") } "%zeropage" -> { - if(directive.parent !is Module) err("this directive may only occur at module level") + if(directive.parent !is Module) + err("this directive may only occur at module level") if(directive.args.size!=1 || directive.args[0].name != "basicsafe" && directive.args[0].name != "floatsafe" && @@ -624,35 +625,41 @@ internal class AstChecker(private val program: Program, err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, dontuse, or full") } "%zpreserved" -> { - if(directive.parent !is Module) err("this directive may only occur at module level") + if(directive.parent !is Module) + err("this directive may only occur at module level") if(directive.args.size!=2 || directive.args[0].int==null || directive.args[1].int==null) err("requires two addresses (start, end)") } "%address" -> { - if(directive.parent !is Module) err("this directive may only occur at module level") + if(directive.parent !is Module) + err("this directive may only occur at module level") if(directive.args.size!=1 || directive.args[0].int == null) err("invalid address directive, expected numeric address argument") } "%import" -> { - if(directive.parent !is Module) err("this directive may only occur at module level") + if(directive.parent !is Module) + err("this directive may only occur at module level") if(directive.args.size!=1 || directive.args[0].name==null) err("invalid import directive, expected module name argument") if(directive.args[0].name == (directive.parent as? Module)?.name) err("invalid import directive, cannot import itself") } "%breakpoint" -> { - if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block") + if(directive.parent !is INameScope || directive.parent is Module) + err("this directive may only occur in a block") if(directive.args.isNotEmpty()) err("invalid breakpoint directive, expected no arguments") } "%asminclude" -> { - if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block") + if(directive.parent !is INameScope || directive.parent is Module) + err("this directive may only occur in a block") if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null) err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"") checkFileExists(directive, directive.args[0].str!!) } "%asmbinary" -> { - if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block") + if(directive.parent !is INameScope || directive.parent is Module) + err("this directive may only occur in a block") val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]" if(directive.args.isEmpty()) err(errormsg) else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg) @@ -662,7 +669,8 @@ internal class AstChecker(private val program: Program, else checkFileExists(directive, directive.args[0].str!!) } "%option" -> { - if(directive.parent !is Block && directive.parent !is Module) err("this directive may only occur in a block or at module level") + if(directive.parent !is Block && directive.parent !is Module) + err("this directive may only occur in a block or at module level") if(directive.args.isEmpty()) err("missing option directive argument(s)") else if(directive.args.map{it.name in setOf("enable_floats", "force_output")}.any { !it }) @@ -678,13 +686,13 @@ internal class AstChecker(private val program: Program, while (definingModule !is Module) definingModule = definingModule.parent if (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile)) - err("included file not found: $filename", directive.position) + errors.err("included file not found: $filename", directive.position) } override fun visit(array: ArrayLiteralValue) { if(array.type.isKnown) { if (!compilerOptions.floats && array.type.typeOrElse(DataType.STRUCT) in setOf(DataType.FLOAT, DataType.ARRAY_F)) { - err("floating point used, but that is not enabled via options", array.position) + errors.err("floating point used, but that is not enabled via options", array.position) } val arrayspec = ArrayIndex.forArray(array) checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array) @@ -702,7 +710,7 @@ internal class AstChecker(private val program: Program, if(expr.operator=="-") { val dt = expr.inferType(program).typeOrElse(DataType.STRUCT) if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { - err("can only take negative of a signed number type", expr.position) + errors.err("can only take negative of a signed number type", expr.position) } } super.visit(expr) @@ -722,56 +730,56 @@ internal class AstChecker(private val program: Program, val constvalRight = expr.right.constValue(program) val divisor = constvalRight?.number?.toDouble() if(divisor==0.0) - err("division by zero", expr.right.position) + errors.err("division by zero", expr.right.position) if(expr.operator=="%") { if ((rightDt != DataType.UBYTE && rightDt != DataType.UWORD) || (leftDt!= DataType.UBYTE && leftDt!= DataType.UWORD)) - err("remainder can only be used on unsigned integer operands", expr.right.position) + errors.err("remainder can only be used on unsigned integer operands", expr.right.position) } } "**" -> { if(leftDt in IntegerDatatypes) - err("power operator requires floating point", expr.position) + errors.err("power operator requires floating point", expr.position) } "and", "or", "xor" -> { // only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1) if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes) - err("logical operator can only be used on boolean operands", expr.right.position) + errors.err("logical operator can only be used on boolean operands", expr.right.position) val constLeft = expr.left.constValue(program) val constRight = expr.right.constValue(program) if(constLeft!=null && constLeft.number.toInt() !in 0..1 || constRight!=null && constRight.number.toInt() !in 0..1) - err("const literal argument of logical operator must be boolean (0 or 1)", expr.position) + errors.err("const literal argument of logical operator must be boolean (0 or 1)", expr.position) } "&", "|", "^" -> { // only integer numeric operands accepted if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes) - err("bitwise operator can only be used on integer operands", expr.right.position) + errors.err("bitwise operator can only be used on integer operands", expr.right.position) } "<<", ">>" -> { // for now, bit-shifts can only shift by a constant number val constRight = expr.right.constValue(program) if(constRight==null) - err("bit-shift can only be done by a constant number (for now)", expr.right.position) + errors.err("bit-shift can only be done by a constant number (for now)", expr.right.position) } } if(leftDt !in NumericDatatypes) - err("left operand is not numeric", expr.left.position) + errors.err("left operand is not numeric", expr.left.position) if(rightDt!in NumericDatatypes) - err("right operand is not numeric", expr.right.position) + errors.err("right operand is not numeric", expr.right.position) if(leftDt!=rightDt) - err("left and right operands aren't the same type", expr.left.position) + errors.err("left and right operands aren't the same type", expr.left.position) super.visit(expr) } override fun visit(typecast: TypecastExpression) { if(typecast.type in IterableDatatypes) - err("cannot type cast to string or array type", typecast.position) + errors.err("cannot type cast to string or array type", typecast.position) super.visit(typecast) } override fun visit(range: RangeExpr) { fun err(msg: String) { - err(msg, range.position) + errors.err(msg, range.position) } super.visit(range) val from = range.from.constValue(program) @@ -791,7 +799,7 @@ internal class AstChecker(private val program: Program, val fromValue = from.number.toInt() val toValue = to.number.toInt() if(fromValue== toValue) - warn("range is just a single value, don't use a loop here", range.position) + errors.warn("range is just a single value, don't use a loop here", range.position) else if(fromValue < toValue && step<=0) err("ascending range requires step > 0") else if(fromValue > toValue && step>=0) @@ -815,7 +823,7 @@ internal class AstChecker(private val program: Program, if(functionCall.target.nameInSource.last()=="sgn") { val sgnArgType = functionCall.args.first().inferType(program) if(sgnArgType.istype(DataType.UBYTE) || sgnArgType.istype(DataType.UWORD)) - warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position) + errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position) } super.visit(functionCall) @@ -827,23 +835,23 @@ internal class AstChecker(private val program: Program, checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position) if(!functionCallStatement.void && targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) { if(targetStatement.returntypes.size==1) - warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position) + errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position) else - warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position) + errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position) } if(functionCallStatement.target.nameInSource.last() == "sort") { // sort is not supported on float arrays val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference if(idref!=null && idref.inferType(program).istype(DataType.ARRAY_F)) { - err("sorting a floating point array is not supported", functionCallStatement.args.first().position) + errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position) } } if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) { // in-place modification, can't be done on literals if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) { - err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position) + errors.err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position) } } super.visit(functionCallStatement) @@ -851,13 +859,13 @@ internal class AstChecker(private val program: Program, private fun checkFunctionCall(target: Statement, args: List, position: Position) { if(target is Label && args.isNotEmpty()) - err("cannot use arguments when calling a label", position) + errors.err("cannot use arguments when calling a label", position) if(target is BuiltinFunctionStatementPlaceholder) { // it's a call to a builtin function. val func = BuiltinFunctions.getValue(target.name) if(args.size!=func.parameters.size) - err("invalid number of arguments", position) + errors.err("invalid number of arguments", position) else { val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD for (arg in args.withIndex().zip(func.parameters)) { @@ -865,7 +873,7 @@ internal class AstChecker(private val program: Program, if (argDt.isKnown && !(argDt.typeOrElse(DataType.STRUCT) isAssignableTo arg.second.possibleDatatypes) && (argDt.typeOrElse(DataType.STRUCT) != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) { - err("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position) + errors.err("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position) } } if(target.name=="swap") { @@ -873,26 +881,26 @@ internal class AstChecker(private val program: Program, val dt1 = args[0].inferType(program) val dt2 = args[1].inferType(program) if (dt1 != dt2) - err("swap requires 2 args of identical type", position) + errors.err("swap requires 2 args of identical type", position) else if (args[0].constValue(program) != null || args[1].constValue(program) != null) - err("swap requires 2 variables, not constant value(s)", position) + errors.err("swap requires 2 variables, not constant value(s)", position) else if(args[0] isSameAs args[1]) - err("swap should have 2 different args", position) + errors.err("swap should have 2 different args", position) else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes) - err("swap requires args of numerical type", position) + errors.err("swap requires args of numerical type", position) } else if(target.name=="all" || target.name=="any") { if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) { - err("any/all on a string is useless (is always true unless the string is empty)", position) + errors.err("any/all on a string is useless (is always true unless the string is empty)", position) } if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) { - err("any/all on a string is useless (is always true unless the string is empty)", position) + errors.err("any/all on a string is useless (is always true unless the string is empty)", position) } } } } else if(target is Subroutine) { if(args.size!=target.parameters.size) - err("invalid number of arguments", position) + errors.err("invalid number of arguments", position) else { for (arg in args.withIndex().zip(target.parameters)) { val argIDt = arg.first.value.inferType(program) @@ -903,26 +911,26 @@ internal class AstChecker(private val program: Program, if(!(argDt isAssignableTo arg.second.type)) { // for asm subroutines having STR param it's okay to provide a UWORD (address value) if(!(target.isAsmSubroutine && arg.second.type == DataType.STR && argDt == DataType.UWORD)) - err("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position) + errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position) } if(target.isAsmSubroutine) { if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) { if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference) - warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position) + errors.warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position) } // check if the argument types match the register(pairs) val asmParamReg = target.asmParameterRegisters[arg.first.index] if(asmParamReg.statusflag!=null) { if(argDt !in ByteDatatypes) - err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for statusflag", position) + errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for statusflag", position) } else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) { if(argDt !in ByteDatatypes) - err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for single register", position) + errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for single register", position) } else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) { if(argDt !in WordDatatypes + IterableDatatypes) - err("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position) + errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position) } } } @@ -936,23 +944,23 @@ internal class AstChecker(private val program: Program, val target = program.namespace.lookup(targetName, postIncrDecr) if(target==null) { val symbol = postIncrDecr.target.identifier!! - err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) + errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) } else { if(target !is VarDecl || target.type== VarDeclType.CONST) { - err("can only increment or decrement a variable", postIncrDecr.position) + errors.err("can only increment or decrement a variable", postIncrDecr.position) } else if(target.datatype !in NumericDatatypes) { - err("can only increment or decrement a byte/float/word variable", postIncrDecr.position) + errors.err("can only increment or decrement a byte/float/word variable", postIncrDecr.position) } } } else if(postIncrDecr.target.arrayindexed != null) { val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace) if(target==null) { - err("undefined symbol", postIncrDecr.position) + errors.err("undefined symbol", postIncrDecr.position) } else { val dt = (target as VarDecl).datatype if(dt !in NumericDatatypes && dt !in ArrayDatatypes) - err("can only increment or decrement a byte/float/word", postIncrDecr.position) + errors.err("can only increment or decrement a byte/float/word", postIncrDecr.position) } } // else if(postIncrDecr.target.memoryAddress != null) { } // a memory location can always be ++/-- @@ -963,29 +971,29 @@ internal class AstChecker(private val program: Program, val target = arrayIndexedExpression.identifier.targetStatement(program.namespace) if(target is VarDecl) { if(target.datatype !in IterableDatatypes) - err("indexing requires an iterable variable", arrayIndexedExpression.position) + errors.err("indexing requires an iterable variable", arrayIndexedExpression.position) val arraysize = target.arraysize?.size() if(arraysize!=null) { // check out of bounds val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt() if(index!=null && (index<0 || index>=arraysize)) - err("array index out of bounds", arrayIndexedExpression.arrayspec.position) + errors.err("array index out of bounds", arrayIndexedExpression.arrayspec.position) } else if(target.datatype == DataType.STR) { if(target.value is StringLiteralValue) { // check string lengths for non-memory mapped strings val stringLen = (target.value as StringLiteralValue).value.length val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt() if (index != null && (index < 0 || index >= stringLen)) - err("index out of bounds", arrayIndexedExpression.arrayspec.position) + errors.err("index out of bounds", arrayIndexedExpression.arrayspec.position) } } } else - err("indexing requires a variable to act upon", arrayIndexedExpression.position) + errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position) // check index value 0..255 val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT) if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE) - err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position) + errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position) super.visit(arrayIndexedExpression) } @@ -993,15 +1001,15 @@ internal class AstChecker(private val program: Program, override fun visit(whenStatement: WhenStatement) { val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT) if(conditionType !in IntegerDatatypes) - err("when condition must be an integer value", whenStatement.position) + errors.err("when condition must be an integer value", whenStatement.position) val choiceValues = whenStatement.choiceValues(program) val occurringValues = choiceValues.map {it.first} val tally = choiceValues.associate { it.second to occurringValues.count { ov->it.first==ov} } tally.filter { it.value>1 }.forEach { - err("choice value occurs multiple times", it.key.position) + errors.err("choice value occurs multiple times", it.key.position) } if(whenStatement.choices.isEmpty()) - err("empty when statement", whenStatement.position) + errors.err("empty when statement", whenStatement.position) super.visit(whenStatement) } @@ -1015,14 +1023,14 @@ internal class AstChecker(private val program: Program, val constvalues = whenChoice.values!!.map { it.constValue(program) } for(constvalue in constvalues) { when { - constvalue == null -> err("choice value must be a constant", whenChoice.position) - constvalue.type !in IntegerDatatypes -> err("choice value must be a byte or word", whenChoice.position) - constvalue.type != conditionType.typeOrElse(DataType.STRUCT) -> err("choice value datatype differs from condition value", whenChoice.position) + constvalue == null -> errors.err("choice value must be a constant", whenChoice.position) + constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position) + constvalue.type != conditionType.typeOrElse(DataType.STRUCT) -> errors.err("choice value datatype differs from condition value", whenChoice.position) } } } else { if(whenChoice !== whenStmt.choices.last()) - err("else choice must be the last one", whenChoice.position) + errors.err("else choice must be the last one", whenChoice.position) } super.visit(whenChoice) } @@ -1030,17 +1038,17 @@ internal class AstChecker(private val program: Program, override fun visit(structDecl: StructDecl) { // a struct can only contain 1 or more vardecls and can not be nested if(structDecl.statements.isEmpty()) - err("struct must contain at least one member", structDecl.position) + errors.err("struct must contain at least one member", structDecl.position) for(member in structDecl.statements){ val decl = member as? VarDecl if(decl==null) - err("struct can only contain variable declarations", structDecl.position) + errors.err("struct can only contain variable declarations", structDecl.position) else { if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE) - err("struct can not contain zeropage members", decl.position) + errors.err("struct can not contain zeropage members", decl.position) if(decl.datatype !in NumericDatatypes) - err("structs can only contain numerical types", decl.position) + errors.err("structs can only contain numerical types", decl.position) } } } @@ -1054,10 +1062,10 @@ internal class AstChecker(private val program: Program, if(stmt is FunctionCallStatement && stmt.target.nameInSource.last()=="exit" && index < statements.lastIndex) - warn("unreachable code, exit call above never returns", statements[index+1].position) + errors.warn("unreachable code, exit call above never returns", statements[index+1].position) if(stmt is Return && index < statements.lastIndex) - warn("unreachable code, return statement above", statements[index+1].position) + errors.warn("unreachable code, return statement above", statements[index+1].position) } } @@ -1065,14 +1073,14 @@ internal class AstChecker(private val program: Program, val targetStatement = target.targetStatement(program.namespace) if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder) return targetStatement - err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position) + errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position) return null } private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteralValue) : Boolean { return if (targetDt == DataType.STR) { if (value.value.length > 255) { - err("string length must be 0-255", value.position) + errors.err("string length must be 0-255", value.position) false } else @@ -1084,7 +1092,7 @@ internal class AstChecker(private val program: Program, private fun checkValueTypeAndRangeArray(targetDt: DataType, struct: StructDecl?, arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean { fun err(msg: String) : Boolean { - err(msg, value.position) + errors.err(msg, value.position) return false } @@ -1172,7 +1180,7 @@ internal class AstChecker(private val program: Program, val vardecl = elt.second as VarDecl val valuetype = elt.first.inferType(program) if (!valuetype.isKnown || !(valuetype.typeOrElse(DataType.STRUCT) isAssignableTo vardecl.datatype)) { - err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position) + errors.err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position) return false } } @@ -1186,7 +1194,7 @@ internal class AstChecker(private val program: Program, private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteralValue) : Boolean { fun err(msg: String) : Boolean { - err(msg, value.position) + errors.err(msg, value.position) return false } when (targetDt) { @@ -1258,7 +1266,7 @@ internal class AstChecker(private val program: Program, else -> throw AstException("invalid array type $type") } if (!correct) - err("array value out of range for type $type", value.position) + errors.err("array value out of range for type $type", value.position) return correct } @@ -1269,7 +1277,7 @@ internal class AstChecker(private val program: Program, position: Position) : Boolean { if(sourceValue is RangeExpr) - err("can't assign a range value to something else", position) + errors.err("can't assign a range value to something else", position) val result = when(targetDatatype) { DataType.BYTE -> sourceDatatype== DataType.BYTE @@ -1287,22 +1295,22 @@ internal class AstChecker(private val program: Program, } false } - else -> err("cannot assign new value to variable of type $targetDatatype", position) + else -> errors.err("cannot assign new value to variable of type $targetDatatype", position) } if(result) return true if((sourceDatatype== DataType.UWORD || sourceDatatype== DataType.WORD) && (targetDatatype== DataType.UBYTE || targetDatatype== DataType.BYTE)) { - err("cannot assign word to byte, use msb() or lsb()?", position) + errors.err("cannot assign word to byte, use msb() or lsb()?", position) } else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes) - err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position) + errors.err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position) else { if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes) - err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position) + errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position) else - err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position) + errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position) } diff --git a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt index e522f5e92..61a7e62b4 100644 --- a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt @@ -12,12 +12,12 @@ import prog8.functions.BuiltinFunctions internal class AstIdentifiersChecker(private val program: Program, - compilerMessages: MutableList) : IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { + private val errors: ErrorReporter) : IAstModifyingVisitor { private var blocks = mutableMapOf() private val vardeclsToAdd = mutableMapOf>() private fun nameError(name: String, position: Position, existing: Statement) { - err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position) + errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position) } override fun visit(module: Module) { @@ -53,15 +53,15 @@ internal class AstIdentifiersChecker(private val program: Program, override fun visit(decl: VarDecl): Statement { // first, check if there are datatype errors on the vardecl - decl.datatypeErrors.forEach { err(it.message, it.position) } + decl.datatypeErrors.forEach { errors.err(it.message, it.position) } // now check the identifier if(decl.name in BuiltinFunctions) // the builtin functions can't be redefined - err("builtin function cannot be redefined", decl.position) + errors.err("builtin function cannot be redefined", decl.position) if(decl.name in CompilationTarget.machine.opcodeNames) - err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) + errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) // is it a struct variable? then define all its struct members as mangled names, // and include the original decl as well. @@ -70,7 +70,7 @@ internal class AstIdentifiersChecker(private val program: Program, return super.visit(decl) // don't do this multiple times if(decl.struct==null) { - err("undefined struct type", decl.position) + errors.err("undefined struct type", decl.position) return super.visit(decl) } @@ -78,7 +78,7 @@ internal class AstIdentifiersChecker(private val program: Program, return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later if(decl.value is NumericLiteralValue) { - err("you cannot initialize a struct using a single value", decl.position) + errors.err("you cannot initialize a struct using a single value", decl.position) return super.visit(decl) } @@ -98,10 +98,10 @@ internal class AstIdentifiersChecker(private val program: Program, override fun visit(subroutine: Subroutine): Statement { if(subroutine.name in CompilationTarget.machine.opcodeNames) { - err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position) + errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position) } else if(subroutine.name in BuiltinFunctions) { // the builtin functions can't be redefined - err("builtin function cannot be redefined", subroutine.position) + errors.err("builtin function cannot be redefined", subroutine.position) } else { // already reported elsewhere: // if (subroutine.parameters.any { it.name in BuiltinFunctions }) @@ -151,7 +151,7 @@ internal class AstIdentifiersChecker(private val program: Program, } if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { - err("asmsub can only contain inline assembly (%asm)", subroutine.position) + errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position) } } return super.visit(subroutine) @@ -159,11 +159,11 @@ internal class AstIdentifiersChecker(private val program: Program, override fun visit(label: Label): Statement { if(label.name in CompilationTarget.machine.opcodeNames) - err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position) + errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position) if(label.name in BuiltinFunctions) { // the builtin functions can't be redefined - err("builtin function cannot be redefined", label.position) + errors.err("builtin function cannot be redefined", label.position) } else { val existing = program.namespace.lookup(listOf(label.name), label) if (existing != null && existing !== label) @@ -179,7 +179,7 @@ internal class AstIdentifiersChecker(private val program: Program, // additional interation count variable in their scope. if(forLoop.loopRegister!=null) { if(forLoop.loopRegister == Register.X) - warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position) + errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position) } else { val loopVar = forLoop.loopVar if (loopVar != null) { @@ -203,7 +203,7 @@ internal class AstIdentifiersChecker(private val program: Program, override fun visit(assignTarget: AssignTarget): AssignTarget { if(assignTarget.register== Register.X) - warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position) + errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position) return super.visit(assignTarget) } @@ -258,7 +258,7 @@ internal class AstIdentifiersChecker(private val program: Program, val vardecl = string.parent as? VarDecl // intern the string; move it into the heap if (string.value.length !in 1..255) - err("string literal length must be between 1 and 255", string.position) + errors.err("string literal length must be between 1 and 255", string.position) return if (vardecl != null) string else @@ -297,7 +297,7 @@ internal class AstIdentifiersChecker(private val program: Program, for(member in structDecl.statements){ val decl = member as? VarDecl if(decl!=null && decl.datatype !in NumericDatatypes) - err("structs can only contain numerical types", decl.position) + errors.err("structs can only contain numerical types", decl.position) } return super.visit(structDecl) diff --git a/compiler/src/prog8/ast/processing/AstRecursionChecker.kt b/compiler/src/prog8/ast/processing/AstRecursionChecker.kt index 4938bbc5f..cc7ff516d 100644 --- a/compiler/src/prog8/ast/processing/AstRecursionChecker.kt +++ b/compiler/src/prog8/ast/processing/AstRecursionChecker.kt @@ -1,14 +1,14 @@ package prog8.ast.processing import prog8.ast.INameScope -import prog8.ast.base.CompilerMessage +import prog8.ast.base.ErrorReporter import prog8.ast.expressions.FunctionCall import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.Subroutine internal class AstRecursionChecker(private val namespace: INameScope, - compilerMessages: MutableList) : IAstVisitor, ErrorReportingVisitor(compilerMessages) { + private val errors: ErrorReporter) : IAstVisitor { private val callGraph = DirectedGraph() fun processMessages(modulename: String) { @@ -16,7 +16,7 @@ internal class AstRecursionChecker(private val namespace: INameScope, if(cycle.isEmpty()) return val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" } - err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", null) + errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", null) } override fun visit(functionCallStatement: FunctionCallStatement) { diff --git a/compiler/src/prog8/ast/processing/IAstModifyingVisitor.kt b/compiler/src/prog8/ast/processing/IAstModifyingVisitor.kt index b93eb1f09..44f888e5d 100644 --- a/compiler/src/prog8/ast/processing/IAstModifyingVisitor.kt +++ b/compiler/src/prog8/ast/processing/IAstModifyingVisitor.kt @@ -2,17 +2,11 @@ package prog8.ast.processing import prog8.ast.Module import prog8.ast.Program -import prog8.ast.base.* +import prog8.ast.base.FatalAstException import prog8.ast.expressions.* import prog8.ast.statements.* -abstract class ErrorReportingVisitor(private val compilerMessages: MutableList) { - internal fun err(msg: String, position: Position?) = compilerMessages.add(CompilerMessage(MessageSeverity.ERROR, msg, position)) - internal fun warn(msg: String, position: Position?) = compilerMessages.add(CompilerMessage(MessageSeverity.WARNING, msg, position)) -} - - interface IAstModifyingVisitor { fun visit(program: Program) { program.modules.forEach { visit(it) } diff --git a/compiler/src/prog8/ast/processing/ImportedModuleDirectiveRemover.kt b/compiler/src/prog8/ast/processing/ImportedModuleDirectiveRemover.kt index e68bf5570..4fe3b3e1b 100644 --- a/compiler/src/prog8/ast/processing/ImportedModuleDirectiveRemover.kt +++ b/compiler/src/prog8/ast/processing/ImportedModuleDirectiveRemover.kt @@ -1,11 +1,11 @@ package prog8.ast.processing import prog8.ast.Module -import prog8.ast.base.CompilerMessage +import prog8.ast.base.ErrorReporter import prog8.ast.statements.Directive import prog8.ast.statements.Statement -internal class ImportedModuleDirectiveRemover(compilerMessages: MutableList) : IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { +internal class ImportedModuleDirectiveRemover(private val errors: ErrorReporter) : IAstModifyingVisitor { /** * Most global directives don't apply for imported modules, so remove them */ @@ -18,7 +18,7 @@ internal class ImportedModuleDirectiveRemover(compilerMessages: MutableList): IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { + private val errors: ErrorReporter): IAstModifyingVisitor { // Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type. // (this includes function call arguments) @@ -125,7 +125,7 @@ internal class TypecastsAdder(private val program: Program, override fun visit(typecast: TypecastExpression): Expression { // warn about any implicit type casts to Float, because that may not be intended if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) { - warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position) + errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position) } return super.visit(typecast) } diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index 89159b499..38694e767 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -30,37 +30,19 @@ fun compileProgram(filepath: Path, var importedFiles: List = emptyList() var success=false - val compilerMessages = mutableListOf() - val alreadyReportedMessages = mutableSetOf() - fun handleCompilerMessages() { - compilerMessages.forEach { - when(it.severity) { - MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red - MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow - } - val msg = "${it.position} ${it.severity} ${it.message}".trim() - if(msg !in alreadyReportedMessages) { - System.err.println(msg) - alreadyReportedMessages.add(msg) - } - System.err.print("\u001b[0m") // reset color - } - val numErrors = compilerMessages.count { it.severity==MessageSeverity.ERROR } - compilerMessages.clear() - if(numErrors>0) - throw ParsingFailedError("There are $numErrors errors.") - } + val errors = ErrorReporter() try { val totalTime = measureTimeMillis { // import main module and everything it needs - val importer = ModuleImporter(compilerMessages) + val importer = ModuleImporter(errors) + errors.handle() println("Parsing...") programAst = Program(moduleName(filepath.fileName), mutableListOf()) importer.importModule(programAst, filepath) - handleCompilerMessages() + errors.handle() importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source } @@ -77,13 +59,13 @@ fun compileProgram(filepath: Path, // always import prog8lib and math importer.importLibraryModule(programAst, "math") importer.importLibraryModule(programAst, "prog8lib") - handleCompilerMessages() + errors.handle() // perform initial syntax checks and constant folding println("Syntax check...") val time1 = measureTimeMillis { - programAst.checkIdentifiers(compilerMessages) - handleCompilerMessages() + programAst.checkIdentifiers(errors) + errors.handle() programAst.makeForeverLoops() } @@ -95,18 +77,18 @@ fun compileProgram(filepath: Path, val time3 = measureTimeMillis { programAst.removeNopsFlattenAnonScopes() programAst.reorderStatements() - programAst.addTypecasts(compilerMessages) - handleCompilerMessages() + programAst.addTypecasts(errors) + errors.handle() } //println(" time3: $time3") val time4 = measureTimeMillis { - programAst.checkValid(compilerOptions, compilerMessages) // check if tree is valid - handleCompilerMessages() + programAst.checkValid(compilerOptions, errors) // check if tree is valid + errors.handle() } //println(" time4: $time4") - programAst.checkIdentifiers(compilerMessages) - handleCompilerMessages() + programAst.checkIdentifiers(errors) + errors.handle() if (optimize) { // optimize the parse tree @@ -114,28 +96,28 @@ fun compileProgram(filepath: Path, while (true) { // keep optimizing expressions and statements until no more steps remain val optsDone1 = programAst.simplifyExpressions() - val optsDone2 = programAst.optimizeStatements(compilerMessages) - handleCompilerMessages() + val optsDone2 = programAst.optimizeStatements(errors) + errors.handle() if (optsDone1 + optsDone2 == 0) break } } - programAst.addTypecasts(compilerMessages) - handleCompilerMessages() + programAst.addTypecasts(errors) + errors.handle() programAst.removeNopsFlattenAnonScopes() - programAst.checkValid(compilerOptions, compilerMessages) // check if final tree is valid - handleCompilerMessages() - programAst.checkRecursion(compilerMessages) // check if there are recursive subroutine calls - handleCompilerMessages() + programAst.checkValid(compilerOptions, errors) // check if final tree is valid + errors.handle() + programAst.checkRecursion(errors) // check if there are recursive subroutine calls + errors.handle() // printAst(programAst) if(writeAssembly) { // asm generation directly from the Ast, no need for intermediate code val zeropage = CompilationTarget.machine.getZeropage(compilerOptions) - programAst.anonscopeVarsCleanup(compilerMessages) - handleCompilerMessages() + programAst.anonscopeVarsCleanup(errors) + errors.handle() val assembly = CompilationTarget.asmGenerator(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize) assembly.assemble(compilerOptions) programName = assembly.name diff --git a/compiler/src/prog8/compiler/Zeropage.kt b/compiler/src/prog8/compiler/Zeropage.kt index bb490799a..5f32d5475 100644 --- a/compiler/src/prog8/compiler/Zeropage.kt +++ b/compiler/src/prog8/compiler/Zeropage.kt @@ -15,7 +15,7 @@ abstract class Zeropage(protected val options: CompilationOptions) { fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size - fun allocate(scopedname: String, datatype: DataType, position: Position?): Int { + fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int { assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"} if(options.zeropage==ZeropageType.DONTUSE) @@ -28,9 +28,9 @@ abstract class Zeropage(protected val options: CompilationOptions) { DataType.FLOAT -> { if (options.floats) { if(position!=null) - printWarning("allocated a large value (float) in zeropage", position) + errors.warn("allocated a large value (float) in zeropage", position) else - printWarning("$scopedname: allocated a large value (float) in zeropage") + errors.warn("$scopedname: allocated a large value (float) in zeropage", null) 5 } else throw CompilerException("floating point option not enabled") } diff --git a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt index 3a3d8b31d..4edb4c30b 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt @@ -11,8 +11,8 @@ import prog8.compiler.target.IAssemblyGenerator import prog8.compiler.target.IAssemblyProgram import prog8.compiler.target.c64.AssemblyProgram import prog8.compiler.target.c64.C64MachineDefinition -import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX +import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX import prog8.compiler.target.c64.Petscii import prog8.compiler.target.generatedLabelPrefix import prog8.functions.BuiltinFunctions @@ -21,7 +21,7 @@ import java.math.RoundingMode import java.nio.file.Path import java.time.LocalDate import java.time.LocalDateTime -import java.util.ArrayDeque +import java.util.* import kotlin.math.absoluteValue @@ -229,7 +229,9 @@ internal class AsmGen(private val program: Program, && variable.datatype != DataType.FLOAT && options.zeropage != ZeropageType.DONTUSE) { try { - val address = zeropage.allocate(fullName, variable.datatype, null) + val errors = ErrorReporter() + val address = zeropage.allocate(fullName, variable.datatype, null, errors) + errors.handle() out("${variable.name} = $address\t; auto zp ${variable.datatype}") // make sure we add the var to the set of zpvars for this block allocatedZeropageVariables[fullName] = Pair(address, variable.datatype) diff --git a/compiler/src/prog8/compiler/target/c64/codegen/AssignmentAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/AssignmentAsmGen.kt index b81d58d8e..6e2846d12 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/AssignmentAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/AssignmentAsmGen.kt @@ -7,12 +7,12 @@ import prog8.ast.statements.AssignTarget import prog8.ast.statements.Assignment import prog8.ast.statements.DirectMemoryWrite import prog8.ast.statements.VarDecl -import prog8.compiler.toHex import prog8.compiler.AssemblyError import prog8.compiler.target.c64.C64MachineDefinition import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX +import prog8.compiler.toHex internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) { diff --git a/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt index d55a26abb..ac9846505 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt @@ -9,13 +9,13 @@ import prog8.ast.base.WordDatatypes import prog8.ast.expressions.* import prog8.ast.statements.AssignTarget import prog8.ast.statements.FunctionCallStatement +import prog8.compiler.AssemblyError import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX import prog8.compiler.toHex -import prog8.compiler.AssemblyError import prog8.functions.FSignature internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) { diff --git a/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt index c5d747e31..84b064ed6 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt @@ -3,13 +3,13 @@ package prog8.compiler.target.c64.codegen import prog8.ast.Program import prog8.ast.base.* import prog8.ast.expressions.* -import prog8.compiler.toHex import prog8.compiler.AssemblyError import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX +import prog8.compiler.toHex import prog8.functions.BuiltinFunctions import kotlin.math.absoluteValue diff --git a/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt index 98daa53b3..eb9765715 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt @@ -8,11 +8,11 @@ import prog8.ast.expressions.RangeExpr import prog8.ast.statements.AssignTarget import prog8.ast.statements.Assignment import prog8.ast.statements.ForLoop +import prog8.compiler.AssemblyError +import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX -import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX import prog8.compiler.toHex -import prog8.compiler.AssemblyError import kotlin.math.absoluteValue // todo choose more efficient comparisons to avoid needless lda's diff --git a/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt index 4d9dac6ec..5d756b5c7 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt @@ -7,10 +7,10 @@ import prog8.ast.expressions.* import prog8.ast.statements.AssignTarget import prog8.ast.statements.Subroutine import prog8.ast.statements.SubroutineParameter -import prog8.compiler.toHex import prog8.compiler.AssemblyError import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX +import prog8.compiler.toHex internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) { diff --git a/compiler/src/prog8/compiler/target/c64/codegen/PostIncrDecrAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/PostIncrDecrAsmGen.kt index 8209958e5..1e6fd0b66 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/PostIncrDecrAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/PostIncrDecrAsmGen.kt @@ -6,9 +6,9 @@ import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.RegisterExpr import prog8.ast.statements.PostIncrDecr -import prog8.compiler.toHex import prog8.compiler.AssemblyError import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage +import prog8.compiler.toHex internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) { diff --git a/compiler/src/prog8/optimizer/Extensions.kt b/compiler/src/prog8/optimizer/Extensions.kt index b1bad11a3..9447cf9f5 100644 --- a/compiler/src/prog8/optimizer/Extensions.kt +++ b/compiler/src/prog8/optimizer/Extensions.kt @@ -2,7 +2,7 @@ package prog8.optimizer import prog8.ast.Program import prog8.ast.base.AstException -import prog8.ast.base.CompilerMessage +import prog8.ast.base.ErrorReporter import prog8.parser.ParsingFailedError @@ -28,8 +28,8 @@ internal fun Program.constantFold() { } -internal fun Program.optimizeStatements(compilerMessages: MutableList): Int { - val optimizer = StatementOptimizer(this, compilerMessages) +internal fun Program.optimizeStatements(errors: ErrorReporter): Int { + val optimizer = StatementOptimizer(this, errors) optimizer.visit(this) modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration diff --git a/compiler/src/prog8/optimizer/StatementOptimizer.kt b/compiler/src/prog8/optimizer/StatementOptimizer.kt index e949b3e69..2dbea4585 100644 --- a/compiler/src/prog8/optimizer/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizer/StatementOptimizer.kt @@ -6,7 +6,6 @@ import prog8.ast.Node import prog8.ast.Program import prog8.ast.base.* import prog8.ast.expressions.* -import prog8.ast.processing.ErrorReportingVisitor import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstVisitor import prog8.ast.statements.* @@ -22,7 +21,7 @@ import kotlin.math.floor internal class StatementOptimizer(private val program: Program, - compilerMessages: MutableList) : IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { + private val errors: ErrorReporter) : IAstModifyingVisitor { var optimizationsDone: Int = 0 private set @@ -82,13 +81,13 @@ internal class StatementOptimizer(private val program: Program, if("force_output" !in block.options()) { if (block.containsNoCodeNorVars()) { optimizationsDone++ - warn("removing empty block '${block.name}'", block.position) + errors.warn("removing empty block '${block.name}'", block.position) return NopStatement.insteadOf(block) } if (block !in callgraph.usedSymbols) { optimizationsDone++ - warn("removing unused block '${block.name}'", block.position) + errors.warn("removing unused block '${block.name}'", block.position) return NopStatement.insteadOf(block) // remove unused block } } @@ -101,7 +100,7 @@ internal class StatementOptimizer(private val program: Program, val forceOutput = "force_output" in subroutine.definingBlock().options() if(subroutine.asmAddress==null && !forceOutput) { if(subroutine.containsNoCodeNorVars()) { - warn("removing empty subroutine '${subroutine.name}'", subroutine.position) + errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position) optimizationsDone++ return NopStatement.insteadOf(subroutine) } @@ -113,7 +112,7 @@ internal class StatementOptimizer(private val program: Program, } if(subroutine !in callgraph.usedSymbols && !forceOutput) { - warn("removing unused subroutine '${subroutine.name}'", subroutine.position) + errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position) optimizationsDone++ return NopStatement.insteadOf(subroutine) } @@ -126,7 +125,7 @@ internal class StatementOptimizer(private val program: Program, val forceOutput = "force_output" in decl.definingBlock().options() if(decl !in callgraph.usedSymbols && !forceOutput) { if(decl.type == VarDeclType.VAR) - warn("removing unused variable ${decl.type} '${decl.name}'", decl.position) + errors.warn("removing unused variable ${decl.type} '${decl.name}'", decl.position) optimizationsDone++ return NopStatement.insteadOf(decl) } @@ -163,7 +162,7 @@ internal class StatementOptimizer(private val program: Program, if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) { val functionName = functionCallStatement.target.nameInSource[0] if (functionName in pureBuiltinFunctions) { - warn("statement has no effect (function return value is discarded)", functionCallStatement.position) + errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position) optimizationsDone++ return NopStatement.insteadOf(functionCallStatement) } @@ -266,12 +265,12 @@ internal class StatementOptimizer(private val program: Program, if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true -> keep only if-part - warn("condition is always true", ifStatement.position) + errors.warn("condition is always true", ifStatement.position) optimizationsDone++ ifStatement.truepart } else { // always false -> keep only else-part - warn("condition is always false", ifStatement.position) + errors.warn("condition is always false", ifStatement.position) optimizationsDone++ ifStatement.elsepart } @@ -315,12 +314,12 @@ internal class StatementOptimizer(private val program: Program, if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true -> print a warning, and optimize into a forever-loop - warn("condition is always true", whileLoop.condition.position) + errors.warn("condition is always true", whileLoop.condition.position) optimizationsDone++ ForeverLoop(whileLoop.body, whileLoop.position) } else { // always false -> remove the while statement altogether - warn("condition is always false", whileLoop.condition.position) + errors.warn("condition is always false", whileLoop.condition.position) optimizationsDone++ NopStatement.insteadOf(whileLoop) } @@ -334,7 +333,7 @@ internal class StatementOptimizer(private val program: Program, if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true -> keep only the statement block (if there are no continue and break statements) - warn("condition is always true", repeatLoop.untilCondition.position) + errors.warn("condition is always true", repeatLoop.untilCondition.position) if(hasContinueOrBreak(repeatLoop.body)) repeatLoop else { @@ -343,7 +342,7 @@ internal class StatementOptimizer(private val program: Program, } } else { // always false -> print a warning, and optimize into a forever loop - warn("condition is always false", repeatLoop.untilCondition.position) + errors.warn("condition is always false", repeatLoop.untilCondition.position) optimizationsDone++ ForeverLoop(repeatLoop.body, repeatLoop.position) } diff --git a/compiler/src/prog8/parser/ModuleParsing.kt b/compiler/src/prog8/parser/ModuleParsing.kt index 98cc159cf..60c4c21f6 100644 --- a/compiler/src/prog8/parser/ModuleParsing.kt +++ b/compiler/src/prog8/parser/ModuleParsing.kt @@ -4,7 +4,7 @@ import org.antlr.v4.runtime.* import prog8.ast.Module import prog8.ast.Program import prog8.ast.antlr.toAst -import prog8.ast.base.CompilerMessage +import prog8.ast.base.ErrorReporter import prog8.ast.base.Position import prog8.ast.base.SyntaxError import prog8.ast.base.checkImportedValid @@ -34,7 +34,7 @@ internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexe internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.') -internal class ModuleImporter(private val compilerMessages: MutableList) { +internal class ModuleImporter(private val errors: ErrorReporter) { internal fun importModule(program: Program, filePath: Path): Module { print("importing '${moduleName(filePath.fileName)}'") @@ -58,10 +58,10 @@ internal class ModuleImporter(private val compilerMessages: MutableList>>", 0, 0, 0)) ), Position("<<>>", 0, 0, 0)) - return executeImportDirective(program, import, Paths.get(""), compilerMessages) + return executeImportDirective(program, import, Paths.get("")) } - internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module { + private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module { val moduleName = moduleName(modulePath.fileName) val lexer = CustomLexer(modulePath, stream) val lexerErrors = LexerErrorListener() @@ -87,7 +87,7 @@ internal class ModuleImporter(private val compilerMessages: MutableList Pair(i, it) } .filter { (it.second as? Directive)?.directive == "%import" } - .forEach { executeImportDirective(program, it.second as Directive, modulePath, compilerMessages) } + .forEach { executeImportDirective(program, it.second as Directive, modulePath) } moduleAst.statements = lines return moduleAst @@ -113,8 +113,7 @@ internal class ModuleImporter(private val compilerMessages: MutableList): Module? { + private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? { if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null) throw SyntaxError("invalid import directive", import.position) val moduleName = import.args[0].name!! @@ -141,7 +140,7 @@ internal class ModuleImporter(private val compilerMessages: MutableList { - zp.allocate("varname", DataType.UBYTE, null) + zp.allocate("varname", DataType.UBYTE, null, errors) } - zp.allocate("varname2", DataType.UBYTE, null) + zp.allocate("varname2", DataType.UBYTE, null, errors) } @Test fun testZpFloatEnable() { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false)) assertFailsWith { - zp.allocate("", DataType.FLOAT, null) + zp.allocate("", DataType.FLOAT, null, errors) } val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true)) assertFailsWith { - zp2.allocate("", DataType.FLOAT, null) + zp2.allocate("", DataType.FLOAT, null, errors) } val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true)) - zp3.allocate("", DataType.FLOAT, null) + zp3.allocate("", DataType.FLOAT, null, errors) } @Test @@ -173,7 +177,7 @@ class TestZeropage { println(zp.free) assertEquals(0, zp.available()) assertFailsWith { - zp.allocate("", DataType.BYTE, null) + zp.allocate("", DataType.BYTE, null, errors) } } @@ -218,19 +222,19 @@ class TestZeropage { assertFailsWith { // in regular zp there aren't 5 sequential bytes free - zp.allocate("", DataType.FLOAT, null) + zp.allocate("", DataType.FLOAT, null, errors) } for (i in 0 until zp.available()) { - val loc = zp.allocate("", DataType.UBYTE, null) + val loc = zp.allocate("", DataType.UBYTE, null, errors) assertTrue(loc > 0) } assertEquals(0, zp.available()) assertFailsWith { - zp.allocate("", DataType.UBYTE, null) + zp.allocate("", DataType.UBYTE, null, errors) } assertFailsWith { - zp.allocate("", DataType.UWORD, null) + zp.allocate("", DataType.UWORD, null, errors) } } @@ -238,29 +242,29 @@ class TestZeropage { fun testFullAllocation() { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false)) assertEquals(238, zp.available()) - val loc = zp.allocate("", DataType.UWORD, null) + val loc = zp.allocate("", DataType.UWORD, null, errors) assertTrue(loc > 3) assertFalse(loc in zp.free) val num = zp.available() / 2 for(i in 0..num-4) { - zp.allocate("", DataType.UWORD, null) + zp.allocate("", DataType.UWORD, null, errors) } assertEquals(6,zp.available()) assertFailsWith { // can't allocate because no more sequential bytes, only fragmented - zp.allocate("", DataType.UWORD, null) + zp.allocate("", DataType.UWORD, null, errors) } for(i in 0..5) { - zp.allocate("", DataType.UBYTE, null) + zp.allocate("", DataType.UBYTE, null, errors) } assertEquals(0, zp.available()) assertFailsWith { // no more space - zp.allocate("", DataType.UBYTE, null) + zp.allocate("", DataType.UBYTE, null, errors) } } @@ -268,16 +272,16 @@ class TestZeropage { fun testEfficientAllocation() { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true)) assertEquals(16, zp.available()) - assertEquals(0x04, zp.allocate("", DataType.WORD, null)) - assertEquals(0x06, zp.allocate("", DataType.UBYTE, null)) - assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null)) - assertEquals(0x94, zp.allocate("", DataType.UWORD, null)) - assertEquals(0xa7, zp.allocate("", DataType.UWORD, null)) - assertEquals(0xa9, zp.allocate("", DataType.UWORD, null)) - assertEquals(0xb5, zp.allocate("", DataType.UWORD, null)) - assertEquals(0xf7, zp.allocate("", DataType.UWORD, null)) - assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null)) - assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null)) + assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors)) + assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors)) + assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors)) + assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors)) + assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors)) + assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors)) + assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors)) + assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors)) + assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors)) + assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors)) assertEquals(0, zp.available()) } }