diff --git a/compiler/src/prog8/ast/base/ErrorReporting.kt b/compiler/src/prog8/ast/base/ErrorReporting.kt index df3191f03..da8e44134 100644 --- a/compiler/src/prog8/ast/base/ErrorReporting.kt +++ b/compiler/src/prog8/ast/base/ErrorReporting.kt @@ -1,37 +1,17 @@ package prog8.ast.base -import prog8.parser.ParsingFailedError - -fun printErrors(errors: List, moduleName: String) { - val reportedMessages = mutableSetOf() - System.err.print("\u001b[91m") // bright red - errors.forEach { - val msg = it.toString() - if(msg !in reportedMessages) { - System.err.println(msg) - reportedMessages.add(msg) - } - } - System.err.print("\u001b[0m") // reset color - if(reportedMessages.isNotEmpty()) - throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.") +enum class MessageSeverity { + WARNING, + ERROR } -fun printWarning(msg: String, position: Position, detailInfo: String?=null) { +class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position?) + + +fun printWarning(message: String, position: Position? = null) { print("\u001b[93m") // bright yellow - print("$position Warning: $msg") - if(detailInfo==null) - print("\n") - else - println(": $detailInfo\n") - print("\u001b[0m") // normal -} - - -fun printWarning(msg: String) { - print("\u001b[93m") // bright yellow - print("Warning: $msg") - print("\u001b[0m\n") // normal + 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 ee07dd6b7..9103faf64 100644 --- a/compiler/src/prog8/ast/base/Extensions.kt +++ b/compiler/src/prog8/ast/base/Extensions.kt @@ -17,17 +17,15 @@ internal fun Program.removeNopsFlattenAnonScopes() { } -internal fun Program.checkValid(compilerOptions: CompilationOptions) { - val checker = AstChecker(this, compilerOptions) +internal fun Program.checkValid(compilerOptions: CompilationOptions, compilerMessages: MutableList) { + val checker = AstChecker(this, compilerOptions, compilerMessages) checker.visit(this) - printErrors(checker.result(), name) } -internal fun Program.anonscopeVarsCleanup() { - val mover = AnonymousScopeVarsCleanup(this) +internal fun Program.anonscopeVarsCleanup(compilerMessages: MutableList) { + val mover = AnonymousScopeVarsCleanup(this, compilerMessages) mover.visit(this) - printErrors(mover.result(), name) } @@ -39,32 +37,30 @@ internal fun Program.reorderStatements() { checker.visit(this) } -internal fun Program.addTypecasts() { - val caster = TypecastsAdder(this) +internal fun Program.addTypecasts(compilerMessages: MutableList) { + val caster = TypecastsAdder(this, compilerMessages) caster.visit(this) } -internal fun Module.checkImportedValid() { - val checker = ImportedModuleDirectiveRemover() +internal fun Module.checkImportedValid(compilerMessages: MutableList) { + val checker = ImportedModuleDirectiveRemover(compilerMessages) checker.visit(this) } -internal fun Program.checkRecursion() { - val checker = AstRecursionChecker(namespace) +internal fun Program.checkRecursion(compilerMessages: MutableList) { + val checker = AstRecursionChecker(namespace, compilerMessages) checker.visit(this) - printErrors(checker.result(), name) + checker.processMessages(name) } -internal fun Program.checkIdentifiers() { - val checker = AstIdentifiersChecker(this) +internal fun Program.checkIdentifiers(compilerMessages: MutableList) { + val checker = AstIdentifiersChecker(this, compilerMessages) checker.visit(this) if(modules.map {it.name}.toSet().size != modules.size) { throw FatalAstException("modules should all be unique") } - - printErrors(checker.result(), name) } diff --git a/compiler/src/prog8/ast/processing/AnonymousScopeVarsCleanup.kt b/compiler/src/prog8/ast/processing/AnonymousScopeVarsCleanup.kt index 38537525b..d3902165d 100644 --- a/compiler/src/prog8/ast/processing/AnonymousScopeVarsCleanup.kt +++ b/compiler/src/prog8/ast/processing/AnonymousScopeVarsCleanup.kt @@ -1,20 +1,14 @@ package prog8.ast.processing import prog8.ast.Program -import prog8.ast.base.AstException -import prog8.ast.base.NameError +import prog8.ast.base.CompilerMessage import prog8.ast.statements.AnonymousScope import prog8.ast.statements.Statement import prog8.ast.statements.VarDecl -class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor { - private val checkResult: MutableList = mutableListOf() +class AnonymousScopeVarsCleanup(val program: Program, compilerMessages: MutableList): IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { private val varsToMove: MutableMap> = mutableMapOf() - fun result(): List { - return checkResult - } - override fun visit(program: Program) { varsToMove.clear() super.visit(program) @@ -25,7 +19,7 @@ class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor { decls.forEach { val existing = existingVariables[it.name] if (existing!=null) { - checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)) + 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 10e3efba8..d1818d2b3 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -12,27 +12,23 @@ import prog8.functions.BuiltinFunctions import java.io.File internal class AstChecker(private val program: Program, - private val compilerOptions: CompilationOptions) : IAstVisitor { - private val checkResult: MutableList = mutableListOf() - - fun result(): List { - return checkResult - } + private val compilerOptions: CompilationOptions, + compilerMessages: MutableList) : IAstVisitor, ErrorReportingVisitor(compilerMessages) { 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) - checkResult.add(SyntaxError("more than one 'main' block", mainBlocks[0].position)) + err("more than one 'main' block", mainBlocks[0].position) for(mainBlock in mainBlocks) { val startSub = mainBlock.subScopes()["start"] as? Subroutine if (startSub == null) { - checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)) + err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position) } else { if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty()) - checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position)) + 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!) @@ -49,7 +45,7 @@ internal class AstChecker(private val program: Program, else -> false } if (!ok) { - checkResult.add(SyntaxError("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position)) + err("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position) break } } @@ -59,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) - checkResult.add(SyntaxError("more than one 'irq' block", irqBlocks[0].position)) + 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()) - checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)) + err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position) } } @@ -77,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.mapTo(checkResult) { SyntaxError("directive can just occur once", it.position) } + entry.value.forEach { err("directive can just occur once", it.position) } } } } @@ -89,18 +85,18 @@ internal class AstChecker(private val program: Program, } if(expectedReturnValues.isEmpty() && returnStmt.value!=null) { - checkResult.add(SyntaxError("invalid number of return values", returnStmt.position)) + err("invalid number of return values", returnStmt.position) } if(expectedReturnValues.isNotEmpty() && returnStmt.value==null) { - checkResult.add(SyntaxError("invalid number of return values", returnStmt.position)) + err("invalid number of return values", returnStmt.position) } if(expectedReturnValues.size==1 && returnStmt.value!=null) { val valueDt = returnStmt.value!!.inferType(program) if(!valueDt.isKnown) { - checkResult.add(ExpressionError("return value type mismatch", returnStmt.value!!.position)) + err("return value type mismatch", returnStmt.value!!.position) } else { if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.STRUCT)) - checkResult.add(ExpressionError("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position)) + err("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position) } } super.visit(returnStmt) @@ -108,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) - checkResult.add(ExpressionError("condition value should be an integer type", ifStatement.condition.position)) + err("condition value should be an integer type", ifStatement.condition.position) super.visit(ifStatement) } override fun visit(forLoop: ForLoop) { if(forLoop.body.containsNoCodeNorVars()) - printWarning("for loop body is empty", forLoop.position) + 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) { - checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position)) + 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) - checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position)) + err("register can only loop over bytes", forLoop.position) if(forLoop.loopRegister!=Register.A) - checkResult.add(ExpressionError("it's only possible to use A as a loop register", forLoop.position)) + 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) { - checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position)) + 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) - checkResult.add(ExpressionError("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)) + 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) - checkResult.add(ExpressionError("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)) + 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) - checkResult.add(ExpressionError("byte loop variable can only loop over bytes", forLoop.position)) + 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) - checkResult.add(ExpressionError("word loop variable can only loop over bytes or words", forLoop.position)) + err("word loop variable can only loop over bytes or words", forLoop.position) } DataType.FLOAT -> { - checkResult.add(ExpressionError("for loop only supports integers", forLoop.position)) + err("for loop only supports integers", forLoop.position) } - else -> checkResult.add(ExpressionError("loop variable must be numeric type", forLoop.position)) + else -> err("loop variable must be numeric type", forLoop.position) } } } @@ -168,18 +164,18 @@ internal class AstChecker(private val program: Program, val targetStatement = checkFunctionOrLabelExists(jump.identifier, jump) if(targetStatement!=null) { if(targetStatement is BuiltinFunctionStatementPlaceholder) - checkResult.add(SyntaxError("can't jump to a builtin function", jump.position)) + err("can't jump to a builtin function", jump.position) } } if(jump.address!=null && (jump.address < 0 || jump.address > 65535)) - checkResult.add(SyntaxError("jump address must be valid integer 0..\$ffff", jump.position)) + 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)) { - checkResult.add(SyntaxError("block memory address must be valid integer 0..\$ffff", block.position)) + err("block memory address must be valid integer 0..\$ffff", block.position) } super.visit(block) @@ -188,14 +184,14 @@ 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) { - checkResult.add(SyntaxError("Labels can only be defined in the scope of a block, a loop body, or within another subroutine", label.position)) + 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) { - checkResult.add(SyntaxError(msg, subroutine.position)) + err(msg, subroutine.position) } if(subroutine.name in BuiltinFunctions) @@ -332,17 +328,17 @@ internal class AstChecker(private val program: Program, override fun visit(repeatLoop: RepeatLoop) { if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) - printWarning("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position) + 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) - checkResult.add(ExpressionError("condition value should be an integer type", repeatLoop.untilCondition.position)) + 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")) - printWarning("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position) + 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) - checkResult.add(ExpressionError("condition value should be an integer type", whileLoop.condition.position)) + err("condition value should be an integer type", whileLoop.condition.position) super.visit(whileLoop) } @@ -352,11 +348,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) - checkResult.add(ExpressionError("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)) + 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)) { - checkResult.add(ExpressionError("return type mismatch", assignment.value.position)) + err("return type mismatch", assignment.value.position) } } } @@ -369,7 +365,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) - checkResult.add(ExpressionError("assignment of different struct types", assignment.position)) + err("assignment of different struct types", assignment.position) } } @@ -382,7 +378,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) - checkResult.add(ExpressionError("address out of range", assignTarget.position)) + err("address out of range", assignTarget.position) } val assignment = assignTarget.parent as Statement @@ -392,16 +388,16 @@ internal class AstChecker(private val program: Program, val targetSymbol = program.namespace.lookup(targetName, assignment) when (targetSymbol) { null -> { - checkResult.add(UndefinedSymbolError(targetIdentifier)) + err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position) return } !is VarDecl -> { - checkResult.add(SyntaxError("assignment LHS must be register or variable", assignment.position)) + err("assignment LHS must be register or variable", assignment.position) return } else -> { if (targetSymbol.type == VarDeclType.CONST) { - checkResult.add(ExpressionError("cannot assign new value to a constant", assignment.position)) + err("cannot assign new value to a constant", assignment.position) return } } @@ -409,7 +405,7 @@ internal class AstChecker(private val program: Program, } val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR) if(targetDt in IterableDatatypes) - checkResult.add(SyntaxError("cannot assign to a string or array type", assignTarget.position)) + err("cannot assign to a string or array type", assignTarget.position) if (assignment is Assignment) { @@ -427,9 +423,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) - checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position)) + err("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)) + 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) @@ -442,17 +438,17 @@ internal class AstChecker(private val program: Program, override fun visit(addressOf: AddressOf) { val variable=addressOf.identifier.targetVarDecl(program.namespace) if(variable==null) - checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position)) + 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) - checkResult.add(ExpressionError("invalid pointer-of operand type", addressOf.position)) + err("invalid pointer-of operand type", addressOf.position) } super.visit(addressOf) } override fun visit(decl: VarDecl) { fun err(msg: String, position: Position?=null) { - checkResult.add(SyntaxError(msg, position ?: decl.position)) + err(msg, position ?: decl.position) } // the initializer value can't refer to the variable itself (recursive definition) @@ -468,19 +464,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) { - checkResult.add(SyntaxError("floating point used, but that is not enabled via options", decl.position)) + err("floating point used, but that is not enabled via options", decl.position) } // ARRAY without size specifier MUST have an iterable initializer value if(decl.isArray && decl.arraysize==null) { if(decl.type== VarDeclType.MEMORY) - checkResult.add(SyntaxError("memory mapped array must have a size specification", decl.position)) + err("memory mapped array must have a size specification", decl.position) if(decl.value==null) { - checkResult.add(SyntaxError("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", decl.position) return } if(decl.value is NumericLiteralValue) { - checkResult.add(SyntaxError("unsized array declaration cannot use a single literal initialization value", decl.position)) + err("unsized array declaration cannot use a single literal initialization value", decl.position) return } if(decl.value is RangeExpr) @@ -546,19 +542,19 @@ internal class AstChecker(private val program: Program, val struct = decl.struct!! val structLv = decl.value as StructLiteralValue if(struct.numberOfElements != structLv.values.size) { - checkResult.add(ExpressionError("struct value has incorrect number of elements", structLv.position)) + 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) { - checkResult.add(ExpressionError("struct literal value for field '${memberdecl.name}' should consist of compile-time constants", value.first.position)) + 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)) { - checkResult.add(ExpressionError("struct member value's type is not compatible with member field '${memberdecl.name}'", value.first.position)) + err("struct member value's type is not compatible with member field '${memberdecl.name}'", value.first.position) return } } @@ -604,7 +600,7 @@ internal class AstChecker(private val program: Program, override fun visit(directive: Directive) { fun err(msg: String) { - checkResult.add(SyntaxError(msg, directive.position)) + err(msg, directive.position) } when(directive.directive) { "%output" -> { @@ -682,13 +678,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)) - checkResult.add(NameError("included file not found: $filename", directive.position)) + 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)) { - checkResult.add(SyntaxError("floating point used, but that is not enabled via options", array.position)) + 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) @@ -706,7 +702,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) { - checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position)) + err("can only take negative of a signed number type", expr.position) } } super.visit(expr) @@ -726,56 +722,56 @@ internal class AstChecker(private val program: Program, val constvalRight = expr.right.constValue(program) val divisor = constvalRight?.number?.toDouble() if(divisor==0.0) - checkResult.add(ExpressionError("division by zero", expr.right.position)) + err("division by zero", expr.right.position) if(expr.operator=="%") { if ((rightDt != DataType.UBYTE && rightDt != DataType.UWORD) || (leftDt!= DataType.UBYTE && leftDt!= DataType.UWORD)) - checkResult.add(ExpressionError("remainder can only be used on unsigned integer operands", expr.right.position)) + err("remainder can only be used on unsigned integer operands", expr.right.position) } } "**" -> { if(leftDt in IntegerDatatypes) - checkResult.add(ExpressionError("power operator requires floating point", expr.position)) + 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) - checkResult.add(ExpressionError("logical operator can only be used on boolean operands", expr.right.position)) + 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) - checkResult.add(ExpressionError("const literal argument of logical operator must be boolean (0 or 1)", expr.position)) + 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) - checkResult.add(ExpressionError("bitwise operator can only be used on integer operands", expr.right.position)) + 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) - checkResult.add(ExpressionError("bit-shift can only be done by a constant number (for now)", expr.right.position)) + err("bit-shift can only be done by a constant number (for now)", expr.right.position) } } if(leftDt !in NumericDatatypes) - checkResult.add(ExpressionError("left operand is not numeric", expr.left.position)) + err("left operand is not numeric", expr.left.position) if(rightDt!in NumericDatatypes) - checkResult.add(ExpressionError("right operand is not numeric", expr.right.position)) + err("right operand is not numeric", expr.right.position) if(leftDt!=rightDt) - checkResult.add(ExpressionError("left and right operands aren't the same type", expr.left.position)) + 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) - checkResult.add(ExpressionError("cannot type cast to string or array type", typecast.position)) + err("cannot type cast to string or array type", typecast.position) super.visit(typecast) } override fun visit(range: RangeExpr) { fun err(msg: String) { - checkResult.add(SyntaxError(msg, range.position)) + err(msg, range.position) } super.visit(range) val from = range.from.constValue(program) @@ -795,7 +791,7 @@ internal class AstChecker(private val program: Program, val fromValue = from.number.toInt() val toValue = to.number.toInt() if(fromValue== toValue) - printWarning("range is just a single value, don't use a loop here", range.position) + 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) @@ -819,7 +815,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)) - printWarning("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position) + warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position) } super.visit(functionCall) @@ -831,23 +827,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) - printWarning("result value of subroutine call is discarded (use void?)", functionCallStatement.position) + warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position) else - printWarning("result values of subroutine call are discarded (use void?)", functionCallStatement.position) + 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)) { - checkResult.add(ExpressionError("sorting a floating point array is not supported", functionCallStatement.args.first().position)) + 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 }) { - checkResult.add(ExpressionError("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position)) + err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position) } } super.visit(functionCallStatement) @@ -855,13 +851,13 @@ internal class AstChecker(private val program: Program, private fun checkFunctionCall(target: Statement, args: List, position: Position) { if(target is Label && args.isNotEmpty()) - checkResult.add(SyntaxError("cannot use arguments when calling a label", position)) + 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) - checkResult.add(SyntaxError("invalid number of arguments", position)) + err("invalid number of arguments", position) else { val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD for (arg in args.withIndex().zip(func.parameters)) { @@ -869,7 +865,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())) { - checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)) + err("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position) } } if(target.name=="swap") { @@ -877,26 +873,26 @@ internal class AstChecker(private val program: Program, val dt1 = args[0].inferType(program) val dt2 = args[1].inferType(program) if (dt1 != dt2) - checkResult.add(ExpressionError("swap requires 2 args of identical type", position)) + err("swap requires 2 args of identical type", position) else if (args[0].constValue(program) != null || args[1].constValue(program) != null) - checkResult.add(ExpressionError("swap requires 2 variables, not constant value(s)", position)) + err("swap requires 2 variables, not constant value(s)", position) else if(args[0] isSameAs args[1]) - checkResult.add(ExpressionError("swap should have 2 different args", position)) + err("swap should have 2 different args", position) else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes) - checkResult.add(ExpressionError("swap requires args of numerical type", position)) + 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) { - checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position)) + 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) { - checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position)) + 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) - checkResult.add(SyntaxError("invalid number of arguments", position)) + err("invalid number of arguments", position) else { for (arg in args.withIndex().zip(target.parameters)) { val argIDt = arg.first.value.inferType(program) @@ -907,26 +903,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)) - checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)) + 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) - printWarning("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) + 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) - checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for statusflag", position)) + 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) - checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for single register", position)) + 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) - checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position)) + err("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position) } } } @@ -939,23 +935,24 @@ internal class AstChecker(private val program: Program, val targetName = postIncrDecr.target.identifier!!.nameInSource val target = program.namespace.lookup(targetName, postIncrDecr) if(target==null) { - checkResult.add(UndefinedSymbolError(postIncrDecr.target.identifier!!)) + val symbol = postIncrDecr.target.identifier!! + err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) } else { if(target !is VarDecl || target.type== VarDeclType.CONST) { - checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position)) + err("can only increment or decrement a variable", postIncrDecr.position) } else if(target.datatype !in NumericDatatypes) { - checkResult.add(SyntaxError("can only increment or decrement a byte/float/word variable", postIncrDecr.position)) + 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) { - checkResult.add(NameError("undefined symbol", postIncrDecr.position)) + err("undefined symbol", postIncrDecr.position) } else { val dt = (target as VarDecl).datatype if(dt !in NumericDatatypes && dt !in ArrayDatatypes) - checkResult.add(SyntaxError("can only increment or decrement a byte/float/word", postIncrDecr.position)) + err("can only increment or decrement a byte/float/word", postIncrDecr.position) } } // else if(postIncrDecr.target.memoryAddress != null) { } // a memory location can always be ++/-- @@ -966,29 +963,29 @@ internal class AstChecker(private val program: Program, val target = arrayIndexedExpression.identifier.targetStatement(program.namespace) if(target is VarDecl) { if(target.datatype !in IterableDatatypes) - checkResult.add(SyntaxError("indexing requires an iterable variable", arrayIndexedExpression.position)) + 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)) - checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position)) + 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)) - checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position)) + err("index out of bounds", arrayIndexedExpression.arrayspec.position) } } } else - checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position)) + 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) - checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)) + err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position) super.visit(arrayIndexedExpression) } @@ -996,15 +993,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) - checkResult.add(SyntaxError("when condition must be an integer value", whenStatement.position)) + 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 { - checkResult.add(SyntaxError("choice value occurs multiple times", it.key.position)) + err("choice value occurs multiple times", it.key.position) } if(whenStatement.choices.isEmpty()) - checkResult.add(SyntaxError("empty when statement", whenStatement.position)) + err("empty when statement", whenStatement.position) super.visit(whenStatement) } @@ -1018,14 +1015,14 @@ internal class AstChecker(private val program: Program, val constvalues = whenChoice.values!!.map { it.constValue(program) } for(constvalue in constvalues) { when { - constvalue == null -> checkResult.add(SyntaxError("choice value must be a constant", whenChoice.position)) - constvalue.type !in IntegerDatatypes -> checkResult.add(SyntaxError("choice value must be a byte or word", whenChoice.position)) - constvalue.type != conditionType.typeOrElse(DataType.STRUCT) -> checkResult.add(SyntaxError("choice value datatype differs from condition value", whenChoice.position)) + 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) } } } else { if(whenChoice !== whenStmt.choices.last()) - checkResult.add(SyntaxError("else choice must be the last one", whenChoice.position)) + err("else choice must be the last one", whenChoice.position) } super.visit(whenChoice) } @@ -1033,17 +1030,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()) - checkResult.add(SyntaxError("struct must contain at least one member", structDecl.position)) + err("struct must contain at least one member", structDecl.position) for(member in structDecl.statements){ val decl = member as? VarDecl if(decl==null) - checkResult.add(SyntaxError("struct can only contain variable declarations", structDecl.position)) + err("struct can only contain variable declarations", structDecl.position) else { if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE) - checkResult.add(SyntaxError("struct can not contain zeropage members", decl.position)) + err("struct can not contain zeropage members", decl.position) if(decl.datatype !in NumericDatatypes) - checkResult.add(SyntaxError("structs can only contain numerical types", decl.position)) + err("structs can only contain numerical types", decl.position) } } } @@ -1057,10 +1054,10 @@ internal class AstChecker(private val program: Program, if(stmt is FunctionCallStatement && stmt.target.nameInSource.last()=="exit" && index < statements.lastIndex) - printWarning("unreachable code", statements[index+1].position, "exit call above never returns") + warn("unreachable code, exit call above never returns", statements[index+1].position) if(stmt is Return && index < statements.lastIndex) - printWarning("unreachable code", statements[index+1].position, "return statement above") + warn("unreachable code, return statement above", statements[index+1].position) } } @@ -1068,14 +1065,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 - checkResult.add(NameError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)) + 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) { - checkResult.add(ExpressionError("string length must be 0-255", value.position)) + err("string length must be 0-255", value.position) false } else @@ -1087,7 +1084,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 { - checkResult.add(ExpressionError(msg, value.position)) + err(msg, value.position) return false } @@ -1175,7 +1172,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)) { - checkResult.add(ExpressionError("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)) + err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position) return false } } @@ -1189,7 +1186,7 @@ internal class AstChecker(private val program: Program, private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteralValue) : Boolean { fun err(msg: String) : Boolean { - checkResult.add(ExpressionError(msg, value.position)) + err(msg, value.position) return false } when (targetDt) { @@ -1261,7 +1258,7 @@ internal class AstChecker(private val program: Program, else -> throw AstException("invalid array type $type") } if (!correct) - checkResult.add(ExpressionError("array value out of range for type $type", value.position)) + err("array value out of range for type $type", value.position) return correct } @@ -1272,7 +1269,7 @@ internal class AstChecker(private val program: Program, position: Position) : Boolean { if(sourceValue is RangeExpr) - checkResult.add(SyntaxError("can't assign a range value to something else", position)) + err("can't assign a range value to something else", position) val result = when(targetDatatype) { DataType.BYTE -> sourceDatatype== DataType.BYTE @@ -1290,22 +1287,22 @@ internal class AstChecker(private val program: Program, } false } - else -> checkResult.add(SyntaxError("cannot assign new value to variable of type $targetDatatype", position)) + else -> 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)) { - checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position)) + err("cannot assign word to byte, use msb() or lsb()?", position) } else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes) - checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)) + 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) - checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position)) + err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position) else - checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)) + 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 41e27c767..e522f5e92 100644 --- a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt @@ -11,19 +11,13 @@ import prog8.compiler.target.CompilationTarget import prog8.functions.BuiltinFunctions -internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor { - - private val checkResult: MutableList = mutableListOf() - +internal class AstIdentifiersChecker(private val program: Program, + compilerMessages: MutableList) : IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { private var blocks = mutableMapOf() private val vardeclsToAdd = mutableMapOf>() - internal fun result(): List { - return checkResult - } - private fun nameError(name: String, position: Position, existing: Statement) { - checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)) + err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position) } override fun visit(module: Module) { @@ -59,15 +53,15 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi override fun visit(decl: VarDecl): Statement { // first, check if there are datatype errors on the vardecl - decl.datatypeErrors.forEach { checkResult.add(it) } + decl.datatypeErrors.forEach { err(it.message, it.position) } // now check the identifier if(decl.name in BuiltinFunctions) // the builtin functions can't be redefined - checkResult.add(NameError("builtin function cannot be redefined", decl.position)) + err("builtin function cannot be redefined", decl.position) if(decl.name in CompilationTarget.machine.opcodeNames) - checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)) + 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. @@ -76,7 +70,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi return super.visit(decl) // don't do this multiple times if(decl.struct==null) { - checkResult.add(NameError("undefined struct type", decl.position)) + err("undefined struct type", decl.position) return super.visit(decl) } @@ -84,7 +78,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later if(decl.value is NumericLiteralValue) { - checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position)) + err("you cannot initialize a struct using a single value", decl.position) return super.visit(decl) } @@ -104,10 +98,10 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi override fun visit(subroutine: Subroutine): Statement { if(subroutine.name in CompilationTarget.machine.opcodeNames) { - checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)) + 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 - checkResult.add(NameError("builtin function cannot be redefined", subroutine.position)) + err("builtin function cannot be redefined", subroutine.position) } else { // already reported elsewhere: // if (subroutine.parameters.any { it.name in BuiltinFunctions }) @@ -157,7 +151,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi } if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { - checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position)) + err("asmsub can only contain inline assembly (%asm)", subroutine.position) } } return super.visit(subroutine) @@ -165,11 +159,11 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi override fun visit(label: Label): Statement { if(label.name in CompilationTarget.machine.opcodeNames) - checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)) + 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 - checkResult.add(NameError("builtin function cannot be redefined", label.position)) + err("builtin function cannot be redefined", label.position) } else { val existing = program.namespace.lookup(listOf(label.name), label) if (existing != null && existing !== label) @@ -185,7 +179,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi // additional interation count variable in their scope. if(forLoop.loopRegister!=null) { if(forLoop.loopRegister == Register.X) - printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position) + 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) { @@ -209,7 +203,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi override fun visit(assignTarget: AssignTarget): AssignTarget { if(assignTarget.register== Register.X) - printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position) + warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position) return super.visit(assignTarget) } @@ -264,7 +258,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi val vardecl = string.parent as? VarDecl // intern the string; move it into the heap if (string.value.length !in 1..255) - checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position)) + err("string literal length must be between 1 and 255", string.position) return if (vardecl != null) string else @@ -303,7 +297,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi for(member in structDecl.statements){ val decl = member as? VarDecl if(decl!=null && decl.datatype !in NumericDatatypes) - checkResult.add(SyntaxError("structs can only contain numerical types", decl.position)) + 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 4f2a13bb5..4938bbc5f 100644 --- a/compiler/src/prog8/ast/processing/AstRecursionChecker.kt +++ b/compiler/src/prog8/ast/processing/AstRecursionChecker.kt @@ -1,21 +1,22 @@ package prog8.ast.processing import prog8.ast.INameScope -import prog8.ast.base.AstException +import prog8.ast.base.CompilerMessage import prog8.ast.expressions.FunctionCall import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.Subroutine -internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisitor { +internal class AstRecursionChecker(private val namespace: INameScope, + compilerMessages: MutableList) : IAstVisitor, ErrorReportingVisitor(compilerMessages) { private val callGraph = DirectedGraph() - internal fun result(): List { + fun processMessages(modulename: String) { val cycle = callGraph.checkForCycle() if(cycle.isEmpty()) - return emptyList() + return val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" } - return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain")) + err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", null) } override fun visit(functionCallStatement: FunctionCallStatement) { @@ -44,7 +45,6 @@ internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisi super.visit(functionCall) } - private class DirectedGraph { private val graph = mutableMapOf>() private var uniqueVertices = mutableSetOf() diff --git a/compiler/src/prog8/ast/processing/IAstModifyingVisitor.kt b/compiler/src/prog8/ast/processing/IAstModifyingVisitor.kt index 58d8e18d8..b93eb1f09 100644 --- a/compiler/src/prog8/ast/processing/IAstModifyingVisitor.kt +++ b/compiler/src/prog8/ast/processing/IAstModifyingVisitor.kt @@ -2,10 +2,17 @@ package prog8.ast.processing import prog8.ast.Module import prog8.ast.Program -import prog8.ast.base.FatalAstException +import prog8.ast.base.* 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 7987457bb..e68bf5570 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.printWarning +import prog8.ast.base.CompilerMessage import prog8.ast.statements.Directive import prog8.ast.statements.Statement -internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor { +internal class ImportedModuleDirectiveRemover(compilerMessages: MutableList) : IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { /** * Most global directives don't apply for imported modules, so remove them */ @@ -18,7 +18,7 @@ internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor { val stmt = sourceStmt.accept(this) if(stmt is Directive && stmt.parent is Module) { if(stmt.directive in moduleLevelDirectives) { - printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive) + warn("ignoring module directive because it was imported", stmt.position) continue } } diff --git a/compiler/src/prog8/ast/processing/TypecastsAdder.kt b/compiler/src/prog8/ast/processing/TypecastsAdder.kt index 4161134c0..4e4465d33 100644 --- a/compiler/src/prog8/ast/processing/TypecastsAdder.kt +++ b/compiler/src/prog8/ast/processing/TypecastsAdder.kt @@ -3,15 +3,16 @@ package prog8.ast.processing import prog8.ast.IFunctionCall import prog8.ast.INameScope import prog8.ast.Program +import prog8.ast.base.CompilerMessage import prog8.ast.base.DataType import prog8.ast.base.FatalAstException -import prog8.ast.base.printWarning import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.functions.BuiltinFunctions -internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor { +internal class TypecastsAdder(private val program: Program, + compilerMessages: MutableList): IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { // 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) @@ -124,7 +125,7 @@ internal class TypecastsAdder(private val program: Program): IAstModifyingVisito 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)) { - printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position) + 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 c3bfc3eba..89159b499 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -8,9 +8,8 @@ import prog8.compiler.target.CompilationTarget import prog8.optimizer.constantFold import prog8.optimizer.optimizeStatements import prog8.optimizer.simplifyExpressions +import prog8.parser.ModuleImporter import prog8.parser.ParsingFailedError -import prog8.parser.importLibraryModule -import prog8.parser.importModule import prog8.parser.moduleName import java.nio.file.Path import kotlin.system.measureTimeMillis @@ -31,13 +30,37 @@ 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.") + } try { val totalTime = measureTimeMillis { // import main module and everything it needs + val importer = ModuleImporter(compilerMessages) + println("Parsing...") programAst = Program(moduleName(filepath.fileName), mutableListOf()) - importModule(programAst, filepath) + importer.importModule(programAst, filepath) + handleCompilerMessages() importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source } @@ -47,19 +70,20 @@ fun compileProgram(filepath: Path, // if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) { - importLibraryModule(programAst, "c64lib") - importLibraryModule(programAst, "c64utils") + importer.importLibraryModule(programAst, "c64lib") + importer.importLibraryModule(programAst, "c64utils") } // always import prog8lib and math - importLibraryModule(programAst, "math") - importLibraryModule(programAst, "prog8lib") - + importer.importLibraryModule(programAst, "math") + importer.importLibraryModule(programAst, "prog8lib") + handleCompilerMessages() // perform initial syntax checks and constant folding println("Syntax check...") val time1 = measureTimeMillis { - programAst.checkIdentifiers() + programAst.checkIdentifiers(compilerMessages) + handleCompilerMessages() programAst.makeForeverLoops() } @@ -71,38 +95,47 @@ fun compileProgram(filepath: Path, val time3 = measureTimeMillis { programAst.removeNopsFlattenAnonScopes() programAst.reorderStatements() - programAst.addTypecasts() + programAst.addTypecasts(compilerMessages) + handleCompilerMessages() } //println(" time3: $time3") val time4 = measureTimeMillis { - programAst.checkValid(compilerOptions) // check if tree is valid + programAst.checkValid(compilerOptions, compilerMessages) // check if tree is valid + handleCompilerMessages() } //println(" time4: $time4") - programAst.checkIdentifiers() + programAst.checkIdentifiers(compilerMessages) + handleCompilerMessages() + if (optimize) { // optimize the parse tree println("Optimizing...") while (true) { // keep optimizing expressions and statements until no more steps remain val optsDone1 = programAst.simplifyExpressions() - val optsDone2 = programAst.optimizeStatements() + val optsDone2 = programAst.optimizeStatements(compilerMessages) + handleCompilerMessages() if (optsDone1 + optsDone2 == 0) break } } - programAst.addTypecasts() + programAst.addTypecasts(compilerMessages) + handleCompilerMessages() programAst.removeNopsFlattenAnonScopes() - programAst.checkValid(compilerOptions) // check if final tree is valid - programAst.checkRecursion() // check if there are recursive subroutine calls + programAst.checkValid(compilerOptions, compilerMessages) // check if final tree is valid + handleCompilerMessages() + programAst.checkRecursion(compilerMessages) // check if there are recursive subroutine calls + handleCompilerMessages() // printAst(programAst) if(writeAssembly) { // asm generation directly from the Ast, no need for intermediate code val zeropage = CompilationTarget.machine.getZeropage(compilerOptions) - programAst.anonscopeVarsCleanup() + programAst.anonscopeVarsCleanup(compilerMessages) + handleCompilerMessages() val assembly = CompilationTarget.asmGenerator(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize) assembly.assemble(compilerOptions) programName = assembly.name diff --git a/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt index 8038bc7cf..c5d747e31 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt @@ -224,8 +224,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge if(value!=null) { if(rightDt in IntegerDatatypes) { val amount = value.number.toInt() - if(amount in powersOfTwo) - printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount") when(rightDt) { DataType.UBYTE -> { if(amount in optimizedByteMultiplications) { diff --git a/compiler/src/prog8/optimizer/Extensions.kt b/compiler/src/prog8/optimizer/Extensions.kt index 6270134e3..b1bad11a3 100644 --- a/compiler/src/prog8/optimizer/Extensions.kt +++ b/compiler/src/prog8/optimizer/Extensions.kt @@ -2,6 +2,7 @@ package prog8.optimizer import prog8.ast.Program import prog8.ast.base.AstException +import prog8.ast.base.CompilerMessage import prog8.parser.ParsingFailedError @@ -27,8 +28,8 @@ internal fun Program.constantFold() { } -internal fun Program.optimizeStatements(): Int { - val optimizer = StatementOptimizer(this) +internal fun Program.optimizeStatements(compilerMessages: MutableList): Int { + val optimizer = StatementOptimizer(this, compilerMessages) 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 f3a0e6ee3..e949b3e69 100644 --- a/compiler/src/prog8/optimizer/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizer/StatementOptimizer.kt @@ -6,6 +6,7 @@ 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.* @@ -20,7 +21,8 @@ import kotlin.math.floor */ -internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor { +internal class StatementOptimizer(private val program: Program, + compilerMessages: MutableList) : IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) { var optimizationsDone: Int = 0 private set @@ -80,13 +82,13 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV if("force_output" !in block.options()) { if (block.containsNoCodeNorVars()) { optimizationsDone++ - printWarning("removing empty block '${block.name}'", block.position) + warn("removing empty block '${block.name}'", block.position) return NopStatement.insteadOf(block) } if (block !in callgraph.usedSymbols) { optimizationsDone++ - printWarning("removing unused block '${block.name}'", block.position) + warn("removing unused block '${block.name}'", block.position) return NopStatement.insteadOf(block) // remove unused block } } @@ -99,7 +101,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV val forceOutput = "force_output" in subroutine.definingBlock().options() if(subroutine.asmAddress==null && !forceOutput) { if(subroutine.containsNoCodeNorVars()) { - printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position) + warn("removing empty subroutine '${subroutine.name}'", subroutine.position) optimizationsDone++ return NopStatement.insteadOf(subroutine) } @@ -111,7 +113,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV } if(subroutine !in callgraph.usedSymbols && !forceOutput) { - printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position) + warn("removing unused subroutine '${subroutine.name}'", subroutine.position) optimizationsDone++ return NopStatement.insteadOf(subroutine) } @@ -124,7 +126,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV val forceOutput = "force_output" in decl.definingBlock().options() if(decl !in callgraph.usedSymbols && !forceOutput) { if(decl.type == VarDeclType.VAR) - printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position) + warn("removing unused variable ${decl.type} '${decl.name}'", decl.position) optimizationsDone++ return NopStatement.insteadOf(decl) } @@ -161,7 +163,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) { val functionName = functionCallStatement.target.nameInSource[0] if (functionName in pureBuiltinFunctions) { - printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position) + warn("statement has no effect (function return value is discarded)", functionCallStatement.position) optimizationsDone++ return NopStatement.insteadOf(functionCallStatement) } @@ -264,12 +266,12 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true -> keep only if-part - printWarning("condition is always true", ifStatement.position) + warn("condition is always true", ifStatement.position) optimizationsDone++ ifStatement.truepart } else { // always false -> keep only else-part - printWarning("condition is always false", ifStatement.position) + warn("condition is always false", ifStatement.position) optimizationsDone++ ifStatement.elsepart } @@ -313,12 +315,12 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true -> print a warning, and optimize into a forever-loop - printWarning("condition is always true", whileLoop.condition.position) + warn("condition is always true", whileLoop.condition.position) optimizationsDone++ ForeverLoop(whileLoop.body, whileLoop.position) } else { // always false -> remove the while statement altogether - printWarning("condition is always false", whileLoop.condition.position) + warn("condition is always false", whileLoop.condition.position) optimizationsDone++ NopStatement.insteadOf(whileLoop) } @@ -332,7 +334,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true -> keep only the statement block (if there are no continue and break statements) - printWarning("condition is always true", repeatLoop.untilCondition.position) + warn("condition is always true", repeatLoop.untilCondition.position) if(hasContinueOrBreak(repeatLoop.body)) repeatLoop else { @@ -341,7 +343,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV } } else { // always false -> print a warning, and optimize into a forever loop - printWarning("condition is always false", repeatLoop.untilCondition.position) + 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 953fcb3ac..98cc159cf 100644 --- a/compiler/src/prog8/parser/ModuleParsing.kt +++ b/compiler/src/prog8/parser/ModuleParsing.kt @@ -4,6 +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.Position import prog8.ast.base.SyntaxError import prog8.ast.base.checkImportedValid @@ -33,114 +34,118 @@ internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexe internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.') -internal fun importModule(program: Program, filePath: Path): Module { - print("importing '${moduleName(filePath.fileName)}'") - if(filePath.parent!=null) { - var importloc = filePath.toString() - val curdir = Paths.get("").toAbsolutePath().toString() - if(importloc.startsWith(curdir)) - importloc = "." + importloc.substring(curdir.length) - println(" (from '$importloc')") - } - else - println("") - if(!Files.isReadable(filePath)) - throw ParsingFailedError("No such file: $filePath") +internal class ModuleImporter(private val compilerMessages: MutableList) { - val input = CharStreams.fromPath(filePath) - return importModule(program, input, filePath, false) -} + internal fun importModule(program: Program, filePath: Path): Module { + print("importing '${moduleName(filePath.fileName)}'") + if(filePath.parent!=null) { + var importloc = filePath.toString() + val curdir = Paths.get("").toAbsolutePath().toString() + if(importloc.startsWith(curdir)) + importloc = "." + importloc.substring(curdir.length) + println(" (from '$importloc')") + } + else + println("") + if(!Files.isReadable(filePath)) + throw ParsingFailedError("No such file: $filePath") -internal fun importLibraryModule(program: Program, name: String): Module? { - val import = Directive("%import", listOf( - DirectiveArg("", name, 42, position = Position("<<>>", 0, 0, 0)) - ), Position("<<>>", 0, 0, 0)) - return executeImportDirective(program, import, Paths.get("")) -} - -internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module { - val moduleName = moduleName(modulePath.fileName) - val lexer = CustomLexer(modulePath, stream) - val lexerErrors = LexerErrorListener() - lexer.addErrorListener(lexerErrors) - val tokens = CommentHandlingTokenStream(lexer) - val parser = prog8Parser(tokens) - val parseTree = parser.module() - val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors - if(numberOfErrors > 0) - throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.") - - // You can do something with the parsed comments: - // tokens.commentTokens().forEach { println(it) } - - // convert to Ast - val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath) - moduleAst.program = program - moduleAst.linkParents(program.namespace) - program.modules.add(moduleAst) - - // accept additional imports - val lines = moduleAst.statements.toMutableList() - lines.asSequence() - .mapIndexed { i, it -> Pair(i, it) } - .filter { (it.second as? Directive)?.directive == "%import" } - .forEach { executeImportDirective(program, it.second as Directive, modulePath) } - - moduleAst.statements = lines - return moduleAst -} - -private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path { - val fileName = "$name.p8" - val locations = mutableListOf(source.parent) - - val propPath = System.getProperty("prog8.libdir") - if(propPath!=null) - locations.add(pathFrom(propPath)) - val envPath = System.getenv("PROG8_LIBDIR") - if(envPath!=null) - locations.add(pathFrom(envPath)) - locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib")) - - locations.forEach { - val file = pathFrom(it.toString(), fileName) - if (Files.isReadable(file)) return file + val input = CharStreams.fromPath(filePath) + return importModule(program, input, filePath, false) } - throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)") -} + internal fun importLibraryModule(program: Program, name: String): Module? { + val import = Directive("%import", listOf( + DirectiveArg("", name, 42, position = Position("<<>>", 0, 0, 0)) + ), Position("<<>>", 0, 0, 0)) + return executeImportDirective(program, import, Paths.get(""), compilerMessages) + } -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!! - if("$moduleName.p8" == import.position.file) - throw SyntaxError("cannot import self", import.position) + internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module { + val moduleName = moduleName(modulePath.fileName) + val lexer = CustomLexer(modulePath, stream) + val lexerErrors = LexerErrorListener() + lexer.addErrorListener(lexerErrors) + val tokens = CommentHandlingTokenStream(lexer) + val parser = prog8Parser(tokens) + val parseTree = parser.module() + val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors + if(numberOfErrors > 0) + throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.") - val existing = program.modules.singleOrNull { it.name == moduleName } - if(existing!=null) - return null + // You can do something with the parsed comments: + // tokens.commentTokens().forEach { println(it) } - val resource = tryGetEmbeddedResource("$moduleName.p8") - val importedModule = - if(resource!=null) { - // load the module from the embedded resource - resource.use { - if(import.args[0].int==42) - println("importing '$moduleName' (library, auto)") - else - println("importing '$moduleName' (library)") - importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true) - } - } else { - val modulePath = discoverImportedModuleFile(moduleName, source, import.position) - importModule(program, modulePath) + // convert to Ast + val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath) + moduleAst.program = program + moduleAst.linkParents(program.namespace) + program.modules.add(moduleAst) + + // accept additional imports + val lines = moduleAst.statements.toMutableList() + lines.asSequence() + .mapIndexed { i, it -> Pair(i, it) } + .filter { (it.second as? Directive)?.directive == "%import" } + .forEach { executeImportDirective(program, it.second as Directive, modulePath, compilerMessages) } + + moduleAst.statements = lines + return moduleAst + } + + private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path { + val fileName = "$name.p8" + val locations = mutableListOf(source.parent) + + val propPath = System.getProperty("prog8.libdir") + if(propPath!=null) + locations.add(pathFrom(propPath)) + val envPath = System.getenv("PROG8_LIBDIR") + if(envPath!=null) + locations.add(pathFrom(envPath)) + locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib")) + + locations.forEach { + val file = pathFrom(it.toString(), fileName) + if (Files.isReadable(file)) return file } - importedModule.checkImportedValid() - return importedModule -} + throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)") + } -internal fun tryGetEmbeddedResource(name: String): InputStream? { - return object{}.javaClass.getResourceAsStream("/prog8lib/$name") + private fun executeImportDirective(program: Program, import: Directive, source: Path, + compilerMessages: MutableList): 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!! + if("$moduleName.p8" == import.position.file) + throw SyntaxError("cannot import self", import.position) + + val existing = program.modules.singleOrNull { it.name == moduleName } + if(existing!=null) + return null + + val resource = tryGetEmbeddedResource("$moduleName.p8") + val importedModule = + if(resource!=null) { + // load the module from the embedded resource + resource.use { + if(import.args[0].int==42) + println("importing '$moduleName' (library, auto)") + else + println("importing '$moduleName' (library)") + importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true) + } + } else { + val modulePath = discoverImportedModuleFile(moduleName, source, import.position) + importModule(program, modulePath) + } + + importedModule.checkImportedValid(compilerMessages) + return importedModule + } + + private fun tryGetEmbeddedResource(name: String): InputStream? { + return object{}.javaClass.getResourceAsStream("/prog8lib/$name") + } } diff --git a/examples/test.p8 b/examples/test.p8 index a677335ae..54d6d1f23 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,6 +1,6 @@ %zeropage basicsafe -main { +main 23232323 { sub start() { c64scr.print("spstart:")