cleanup of error reporting

This commit is contained in:
Irmen de Jong 2020-03-14 21:52:29 +01:00
parent a326ffa00a
commit baf5d3041a
15 changed files with 364 additions and 356 deletions

View File

@ -1,37 +1,17 @@
package prog8.ast.base
import prog8.parser.ParsingFailedError
fun printErrors(errors: List<Any>, moduleName: String) {
val reportedMessages = mutableSetOf<String>()
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
}

View File

@ -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<CompilerMessage>) {
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<CompilerMessage>) {
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<CompilerMessage>) {
val caster = TypecastsAdder(this, compilerMessages)
caster.visit(this)
}
internal fun Module.checkImportedValid() {
val checker = ImportedModuleDirectiveRemover()
internal fun Module.checkImportedValid(compilerMessages: MutableList<CompilerMessage>) {
val checker = ImportedModuleDirectiveRemover(compilerMessages)
checker.visit(this)
}
internal fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace)
internal fun Program.checkRecursion(compilerMessages: MutableList<CompilerMessage>) {
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<CompilerMessage>) {
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)
}

View File

@ -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<AstException> = mutableListOf()
class AnonymousScopeVarsCleanup(val program: Program, compilerMessages: MutableList<CompilerMessage>): IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) {
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
fun result(): List<AstException> {
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
}
}

View File

@ -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<AstException> = mutableListOf()
fun result(): List<AstException> {
return checkResult
}
private val compilerOptions: CompilationOptions,
compilerMessages: MutableList<CompilerMessage>) : 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<Expression>, 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)
}

View File

@ -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<AstException> = mutableListOf()
internal class AstIdentifiersChecker(private val program: Program,
compilerMessages: MutableList<CompilerMessage>) : IAstModifyingVisitor, ErrorReportingVisitor(compilerMessages) {
private var blocks = mutableMapOf<String, Block>()
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
internal fun result(): List<AstException> {
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)

View File

@ -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<CompilerMessage>) : IAstVisitor, ErrorReportingVisitor(compilerMessages) {
private val callGraph = DirectedGraph<INameScope>()
internal fun result(): List<AstException> {
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<VT> {
private val graph = mutableMapOf<VT, MutableSet<VT>>()
private var uniqueVertices = mutableSetOf<VT>()

View File

@ -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<CompilerMessage>) {
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) }

View File

@ -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<CompilerMessage>) : 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
}
}

View File

@ -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<CompilerMessage>): 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)
}

View File

@ -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<Path> = emptyList()
var success=false
val compilerMessages = mutableListOf<CompilerMessage>()
val alreadyReportedMessages = mutableSetOf<String>()
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

View File

@ -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) {

View File

@ -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<CompilerMessage>): Int {
val optimizer = StatementOptimizer(this, compilerMessages)
optimizer.visit(this)
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration

View File

@ -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<CompilerMessage>) : 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)
}

View File

@ -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<CompilerMessage>) {
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("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 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("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 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<CompilerMessage>): 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")
}
}

View File

@ -1,6 +1,6 @@
%zeropage basicsafe
main {
main 23232323 {
sub start() {
c64scr.print("spstart:")