improve undefined symbol error reporting

This commit is contained in:
Irmen de Jong 2018-09-06 00:08:16 +02:00
parent 831d41dbc6
commit f23808eaae
5 changed files with 67 additions and 46 deletions

View File

@ -30,7 +30,7 @@
const word max1 = max([-1,-2,3,99+22])
const word min1 = min([1,2,3,99+22])
word dinges = round(not_main.len1)
A = X>2
X = Y>Y

View File

@ -60,7 +60,7 @@ fun main(args: Array<String>) {
// compile the syntax tree into intermediate form, and optimize that
val compiler = Compiler(compilerOptions, globalNamespaceAfterOptimize)
val compiler = Compiler(compilerOptions)
val intermediate = compiler.compile(moduleAst)
intermediate.optimize()

View File

@ -70,13 +70,16 @@ class NameError(override var message: String, val position: Position?) : AstExce
}
}
class ExpressionException(message: String, val position: Position?) : AstException(message) {
open class ExpressionException(message: String, val position: Position?) : AstException(message) {
override fun toString(): String {
val location = position?.toString() ?: ""
return "$location Error: $message"
}
}
class UndefinedSymbolException(val symbol: IdentifierReference)
: ExpressionException("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
@ -273,8 +276,8 @@ interface INameScope {
namespace.labelsAndVariables().forEach {
println(" ".repeat(4 * (1 + indent)) + "${it.key} -> ${it.value::class.simpleName} at ${it.value.position}")
}
namespace.subScopes().forEach {
printNames(indent+1, it.value)
namespace.statements.filter { it is INameScope }.forEach {
printNames(indent+1, it as INameScope)
}
}
printNames(0, this)
@ -347,47 +350,49 @@ class Module(override val name: String,
processor.process(this)
}
override fun definingScope(): INameScope = GlobalNamespace("<<<global>>>", statements, position)
override fun definingScope(): INameScope = GlobalNamespace("<<<global>>>", statements, position)
override fun usedNames(): Set<String> = throw NotImplementedError("not implemented on sub-scopes")
override fun registerUsedName(name: String) = throw NotImplementedError("not implemented on sub-scopes")
private class GlobalNamespace(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position?) : INameScope {
}
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main") // main is always used
override fun usedNames(): Set<String> = scopedNamesUsed
private class GlobalNamespace(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position?) : INameScope {
override fun lookup(scopedName: List<String>, statement: Node): IStatement? {
if(BuiltinFunctionNames.contains(scopedName.last())) {
// builtin functions always exist, return a dummy statement for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}")
builtinPlaceholder.position = statement.position
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
val stmt = super.lookup(scopedName, statement)
if(stmt!=null) {
val targetScopedName = when(stmt) {
is Label -> stmt.scopedname
is VarDecl -> stmt.scopedname
is Block -> stmt.scopedname
is Subroutine -> stmt.scopedname
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
}
registerUsedName(targetScopedName.joinToString("."))
}
return stmt
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main") // main is always used
override fun usedNames(): Set<String> = scopedNamesUsed
override fun lookup(scopedName: List<String>, statement: Node): IStatement? {
if(BuiltinFunctionNames.contains(scopedName.last())) {
// builtin functions always exist, return a dummy statement for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}")
builtinPlaceholder.position = statement.position
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
override fun registerUsedName(name: String) {
// make sure to also register each scope separately
scopedNamesUsed.add(name)
if(name.contains('.'))
registerUsedName(name.substringBeforeLast('.'))
val stmt = super.lookup(scopedName, statement)
if(stmt!=null) {
val targetScopedName = when(stmt) {
is Label -> stmt.scopedname
is VarDecl -> stmt.scopedname
is Block -> stmt.scopedname
is Subroutine -> stmt.scopedname
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
}
registerUsedName(targetScopedName.joinToString("."))
}
return stmt
}
override fun registerUsedName(name: String) {
// make sure to also register each scope separately
scopedNamesUsed.add(name)
if(name.contains('.'))
registerUsedName(name.substringBeforeLast('.'))
}
}
@ -678,10 +683,10 @@ data class IdentifierReference(val nameInSource: List<String>) : IExpression {
this.parent = parent
}
override fun constValue(namespace: INameScope): LiteralValue? {
val node = namespace.lookup(nameInSource, this)
?:
throw ExpressionException("undefined symbol: ${nameInSource.joinToString(".")}", position)
?: throw UndefinedSymbolException(this)
val vardecl = node as? VarDecl
if(vardecl==null) {
throw ExpressionException("name should be a constant, instead of: ${node::class.simpleName}", position)

View File

@ -88,9 +88,16 @@ data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, va
}
class Compiler(private val options: CompilationOptions, val namespace: INameScope) {
class Compiler(private val options: CompilationOptions) {
fun compile(module: Module) : IntermediateForm {
println("\nCompiling parsed source code to intermediate code...")
// make sure the 'main' block is the first block. Statement even.
val mainBlock = module.statements.single { it is Block && it.name=="main" }
module.statements.remove(mainBlock)
module.statements.add(0, mainBlock)
val namespace = module.definingScope()
// todo
namespace.debugPrint()

View File

@ -11,7 +11,7 @@ fun Module.optimizeExpressions(globalNamespace: INameScope) {
try {
this.process(optimizer)
} catch (ax: AstException) {
optimizer.errors.add(ax)
optimizer.addError(ax)
}
if(optimizer.optimizationsDone==0)
@ -56,6 +56,15 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf()
private val reportedErrorMessages = mutableSetOf<String>()
fun addError(x: AstException) {
// check that we don't add the same error more than once
if(!reportedErrorMessages.contains(x.message)) {
reportedErrorMessages.add(x.message)
errors.add(x)
}
}
override fun process(decl: VarDecl): IStatement {
// the initializer value can't refer to the variable itself (recursive definition)
@ -101,7 +110,7 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
return try {
identifier.constValue(globalNamespace) ?: identifier
} catch (ax: AstException) {
errors.add(ax)
addError(ax)
identifier
}
}
@ -111,7 +120,7 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
super.process(functionCall)
functionCall.constValue(globalNamespace) ?: functionCall
} catch (ax: AstException) {
errors.add(ax)
addError(ax)
functionCall
}
}
@ -166,7 +175,7 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
}
return expr
} catch (ax: AstException) {
errors.add(ax)
addError(ax)
expr
}
}
@ -191,7 +200,7 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
else -> expr
}
} catch (ax: AstException) {
errors.add(ax)
addError(ax)
expr
}
}
@ -231,7 +240,7 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
}
return range
} catch (ax: AstException) {
errors.add(ax)
addError(ax)
range
}
}