mirror of
https://github.com/irmen/prog8.git
synced 2024-10-19 22:24:17 +00:00
cleanup of error reporting
This commit is contained in:
parent
a326ffa00a
commit
baf5d3041a
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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>()
|
||||
|
@ -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) }
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,7 +34,9 @@ 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 {
|
||||
internal class ModuleImporter(private val compilerMessages: MutableList<CompilerMessage>) {
|
||||
|
||||
internal fun importModule(program: Program, filePath: Path): Module {
|
||||
print("importing '${moduleName(filePath.fileName)}'")
|
||||
if(filePath.parent!=null) {
|
||||
var importloc = filePath.toString()
|
||||
@ -49,16 +52,16 @@ internal fun importModule(program: Program, filePath: Path): Module {
|
||||
|
||||
val input = CharStreams.fromPath(filePath)
|
||||
return importModule(program, input, filePath, false)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun importLibraryModule(program: Program, name: String): Module? {
|
||||
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(""))
|
||||
}
|
||||
return executeImportDirective(program, import, Paths.get(""), compilerMessages)
|
||||
}
|
||||
|
||||
internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
|
||||
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()
|
||||
@ -84,13 +87,13 @@ internal fun importModule(program: Program, stream: CharStream, modulePath: Path
|
||||
lines.asSequence()
|
||||
.mapIndexed { i, it -> Pair(i, it) }
|
||||
.filter { (it.second as? Directive)?.directive == "%import" }
|
||||
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
|
||||
.forEach { executeImportDirective(program, it.second as Directive, modulePath, compilerMessages) }
|
||||
|
||||
moduleAst.statements = lines
|
||||
return moduleAst
|
||||
}
|
||||
}
|
||||
|
||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||
val fileName = "$name.p8"
|
||||
val locations = mutableListOf(source.parent)
|
||||
|
||||
@ -108,9 +111,10 @@ private fun discoverImportedModuleFile(name: String, source: Path, position: Pos
|
||||
}
|
||||
|
||||
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
|
||||
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!!
|
||||
@ -137,10 +141,11 @@ private fun executeImportDirective(program: Program, import: Directive, source:
|
||||
importModule(program, modulePath)
|
||||
}
|
||||
|
||||
importedModule.checkImportedValid()
|
||||
importedModule.checkImportedValid(compilerMessages)
|
||||
return importedModule
|
||||
}
|
||||
}
|
||||
|
||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
private fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
main 23232323 {
|
||||
|
||||
sub start() {
|
||||
c64scr.print("spstart:")
|
||||
|
Loading…
Reference in New Issue
Block a user