From f23808eaae89b43b26138d0d059a070aa974b113 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 6 Sep 2018 00:08:16 +0200 Subject: [PATCH] improve undefined symbol error reporting --- il65/examples/test.ill | 2 +- il65/src/il65/Main.kt | 2 +- il65/src/il65/ast/AST.kt | 79 ++++++++++--------- il65/src/il65/compiler/Compiler.kt | 9 ++- .../il65/optimizing/ExpressionOptimizer.kt | 21 +++-- 5 files changed, 67 insertions(+), 46 deletions(-) diff --git a/il65/examples/test.ill b/il65/examples/test.ill index f4154154c..1f9e1aef3 100644 --- a/il65/examples/test.ill +++ b/il65/examples/test.ill @@ -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 diff --git a/il65/src/il65/Main.kt b/il65/src/il65/Main.kt index a503422a5..e8ab1a622 100644 --- a/il65/src/il65/Main.kt +++ b/il65/src/il65/Main.kt @@ -60,7 +60,7 @@ fun main(args: Array) { // 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() diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index d3044078a..80866c40c 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -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("<<>>", statements, position) + override fun definingScope(): INameScope = GlobalNamespace("<<>>", statements, position) override fun usedNames(): Set = 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, - override val position: Position?) : INameScope { +} - private val scopedNamesUsed: MutableSet = mutableSetOf("main") // main is always used - override fun usedNames(): Set = scopedNamesUsed +private class GlobalNamespace(override val name: String, + override var statements: MutableList, + override val position: Position?) : INameScope { - override fun lookup(scopedName: List, 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 = mutableSetOf("main") // main is always used + + override fun usedNames(): Set = scopedNamesUsed + + override fun lookup(scopedName: List, 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) : 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) diff --git a/il65/src/il65/compiler/Compiler.kt b/il65/src/il65/compiler/Compiler.kt index c34ed1004..95525adfe 100644 --- a/il65/src/il65/compiler/Compiler.kt +++ b/il65/src/il65/compiler/Compiler.kt @@ -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() diff --git a/il65/src/il65/optimizing/ExpressionOptimizer.kt b/il65/src/il65/optimizing/ExpressionOptimizer.kt index 9d1bb56d0..2d6617af3 100644 --- a/il65/src/il65/optimizing/ExpressionOptimizer.kt +++ b/il65/src/il65/optimizing/ExpressionOptimizer.kt @@ -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 = mutableListOf() + private val reportedErrorMessages = mutableSetOf() + + 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 } }