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