cleanup of error reporting

This commit is contained in:
Irmen de Jong 2020-03-14 23:47:26 +01:00
parent baf5d3041a
commit 6bd99d63b4
28 changed files with 319 additions and 308 deletions

View File

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

View File

@ -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
}

View File

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

View File

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

View File

@ -1,7 +1,7 @@
package prog8.ast.expressions
import java.util.Objects
import prog8.ast.base.DataType
import java.util.*
object InferredTypes {

View File

@ -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
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}
}

View File

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

View File

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

View File

@ -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")
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}

View File

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

View File

@ -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.*

View File

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

View File

@ -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())
}
}