Commit 974a9118 authored by Kunshan Wang's avatar Kunshan Wang

Nicer error messages

parent ae9fdfe8
......@@ -15,31 +15,31 @@ topLevelDef
;
typeDef
: '.typedef' nam=GLOBAL_NAME '=' ctor=typeConstructor
: '.typedef' nam=globalName '=' ctor=typeConstructor
;
funcSigDef
: '.funcsig' nam=GLOBAL_NAME '=' ctor=funcSigConstructor
: '.funcsig' nam=globalName '=' ctor=funcSigConstructor
;
constDef
: '.const' nam=GLOBAL_NAME '<' ty=type '>' '=' ctor=constConstructor
: '.const' nam=globalName '<' ty=type '>' '=' ctor=constConstructor
;
globalDef
: '.global' nam=GLOBAL_NAME '<' ty=type '>'
: '.global' nam=globalName '<' ty=type '>'
;
funcDecl
: '.funcdecl' nam=GLOBAL_NAME '<' sig=funcSig '>'
: '.funcdecl' nam=globalName '<' sig=funcSig '>'
;
funcDef
: '.funcdef' nam=GLOBAL_NAME 'VERSION' ver=name '<' sig=funcSig '>' body=funcBody
: '.funcdef' nam=globalName 'VERSION' ver=name '<' sig=funcSig '>' body=funcBody
;
funcExpDef
: '.expose' nam=GLOBAL_NAME '=' funcName=GLOBAL_NAME callConv=flag cookie=GLOBAL_NAME
: '.expose' nam=globalName '=' func callConv=flag cookie=constant
;
typeConstructor
......@@ -70,24 +70,40 @@ constConstructor
: intLiteral # CtorInt
| floatLiteral # CtorFloat
| doubleLiteral # CtorDouble
| '{' GLOBAL_NAME* '}' # CtorList
| '{' globalVar* '}' # CtorList
| 'NULL' # CtorNull
;
type
: GLOBAL_NAME
: globalName
;
funcSig
: GLOBAL_NAME
: globalName
;
constant
: GLOBAL_NAME
: globalName
;
paramList
: '(' name* ')'
globalCell
: globalName
;
func
: globalName
;
funcVer
: globalName
;
expFunc
: globalName
;
globalVar
: globalName
;
funcBody
......@@ -187,7 +203,7 @@ instBody
| 'SWAPSTACK' swappee=value curStackClause newStackClause excClause keepAliveClause # InstSwapStack
// Common Instructions
| 'COMMINST' nam=GLOBAL_NAME flagList? typeList? funcSigList? argList? excClause keepAliveClause # InstCommInst
| 'COMMINST' nam=globalName flagList? typeList? funcSigList? argList? excClause keepAliveClause # InstCommInst
;
retVals
......@@ -296,10 +312,13 @@ doubleLiteral
;
name
: GLOBAL_NAME
| LOCAL_NAME
: globalName
| localName
;
globalName : GLOBAL_NAME;
localName : LOCAL_NAME;
// LEXER
INT_DEC
......
......@@ -12,13 +12,11 @@ abstract class Bundle {
* + typeNs // All types
* + funcSigNs // All function signatures
* + funcVerNs // All function versions
* + varNs // All variables, global or local
* + globalVarNs // Global variables
* + constantNs // Constants
* + globalCellNs // Global cells
* + funcNs // Functions
* + expFuncNs // Exposed functions
* + localVarNs // Local variables (per basic block)
* + globalVarNs // Global variables
* + constantNs // Constants
* + globalCellNs // Global cells
* + funcNs // Functions
* + expFuncNs // Exposed functions
* + bbNs // Basic blocks (per function version)
* + instNs // All instructions
* + localInstNs // Instructions in a basic block (per basic block)
......@@ -26,20 +24,21 @@ abstract class Bundle {
* bbNs and localVarNs are part of particular FuncVers and BBs.
*/
val allNs = new NestedNamespace[Identified](None)
val allNs = new NestedNamespace[Identified](None, "Mu entity")
val typeNs = allNs.makeSubSpace[Type]()
val funcSigNs = allNs.makeSubSpace[FuncSig]()
val funcVerNs = allNs.makeSubSpace[FuncVer]()
val varNs = allNs.makeSubSpace[SSAVariable]()
val typeNs = allNs.makeSubSpace[Type]("type")
val funcSigNs = allNs.makeSubSpace[FuncSig]("function signature")
val funcVerNs = allNs.makeSubSpace[FuncVer]("function version")
val globalVarNs = varNs.makeSubSpace[GlobalVariable]()
val constantNs = globalVarNs.makeSubSpace[Constant]()
val globalCellNs = globalVarNs.makeSubSpace[GlobalCell]()
val funcNs = globalVarNs.makeSubSpace[Function]()
val expFuncNs = globalVarNs.makeSubSpace[ExposedFunc]()
val globalVarNs = allNs.makeSubSpace[GlobalVariable]("global variable")
val constantNs = globalVarNs.makeSubSpace[Constant]("constant")
val globalCellNs = globalVarNs.makeSubSpace[GlobalCell]("global cell")
val funcNs = globalVarNs.makeSubSpace[Function]("function")
val expFuncNs = globalVarNs.makeSubSpace[ExposedFunc]("exposed function")
val instNs = allNs.makeSubSpace[Instruction]()
val instNs = allNs.makeSubSpace[Instruction]("instruction")
val sourceInfoRepo = new SourceInfoRepo()
}
/**
......@@ -64,7 +63,7 @@ class TrantientBundle extends Bundle {
* This kind of bundle holds the global state. Functions and versions are fully merged.
*/
class GlobalBundle extends Bundle {
private def simpleMerge[T <: Identified](oldNs: Namespace[T], newNs: Namespace[T]) {
private def simpleMerge[T <: Identified](oldNs: Namespace[T], newNs: Namespace[T], newSourceInfoRepo: SourceInfoRepo) {
for (cand <- newNs.all) {
try {
oldNs.add(cand)
......@@ -81,10 +80,7 @@ class GlobalBundle extends Bundle {
// }
// assertPresent(oldNs.asInstanceOf[NestedNamespace[T]], cand)
} catch {
case e: NameConflictException =>
throw new IllegalRedefinitionException(
"Redefinition of type, function signature, constant or" +
" global cell is not allowed: %s".format(cand.repr), e);
case e: NameConflictException => throw e.toIllegalRedefinitionException(newSourceInfoRepo, sourceInfoRepo)
}
}
}
......@@ -96,6 +92,7 @@ class GlobalBundle extends Bundle {
}
private def mergeLocalNamespaces(newBundle: TrantientBundle) {
try {
for (fv <- newBundle.funcVerNs.all) {
fv.bbNs.reparent(allNs)
for (bb <- fv.bbs) {
......@@ -103,22 +100,27 @@ class GlobalBundle extends Bundle {
bb.localInstNs.reparent(allNs)
}
}
} catch {
case e: NameConflictException => throw e.toIllegalRedefinitionException(newBundle.sourceInfoRepo, sourceInfoRepo)
}
}
def merge(newBundle: TrantientBundle) {
// Only merge leaves
simpleMerge(typeNs, newBundle.typeNs)
simpleMerge(funcSigNs, newBundle.funcSigNs)
simpleMerge(funcVerNs, newBundle.funcVerNs)
simpleMerge(constantNs, newBundle.constantNs)
simpleMerge(globalCellNs, newBundle.globalCellNs)
simpleMerge(funcNs, newBundle.funcNs)
simpleMerge(expFuncNs, newBundle.expFuncNs)
simpleMerge(instNs, newBundle.instNs)
simpleMerge(typeNs, newBundle.typeNs, newBundle.sourceInfoRepo)
simpleMerge(funcSigNs, newBundle.funcSigNs, newBundle.sourceInfoRepo)
simpleMerge(funcVerNs, newBundle.funcVerNs, newBundle.sourceInfoRepo)
simpleMerge(constantNs, newBundle.constantNs, newBundle.sourceInfoRepo)
simpleMerge(globalCellNs, newBundle.globalCellNs, newBundle.sourceInfoRepo)
simpleMerge(funcNs, newBundle.funcNs, newBundle.sourceInfoRepo)
simpleMerge(expFuncNs, newBundle.expFuncNs, newBundle.sourceInfoRepo)
simpleMerge(instNs, newBundle.instNs, newBundle.sourceInfoRepo)
redefineFunctions(newBundle.funcVerNs)
mergeLocalNamespaces(newBundle)
sourceInfoRepo.merge(newBundle.sourceInfoRepo)
}
}
\ No newline at end of file
......@@ -8,4 +8,15 @@ class TypeResolutionException(message: String = null, cause: Throwable = null) e
class IllegalRedefinitionException(message: String = null, cause: Throwable = null) extends BundleLoadingException(message, cause)
class NameConflictException(message: String = null, cause: Throwable = null) extends UvmException(message, cause)
class NameConflictException(val kind: String, val whatConflicts: String, val newItem: Identified, val oldItem: Identified, cause: Throwable = null) extends {
val message = "Conflict: %s %s has the same %s as %s".format(kind, newItem.repr, whatConflicts, oldItem.repr)
} with UvmException(message, cause) {
def toIllegalRedefinitionException(newSourceInfoRepo: SourceInfoRepo, oldSourceInfoRepo: SourceInfoRepo): IllegalRedefinitionException = {
val oldSrcInfo = oldSourceInfoRepo(oldItem)
val newSrcInfo = newSourceInfoRepo(newItem)
throw new IllegalRedefinitionException(
"Cannot redefine %s %s\n%s\nwhich is already defined %s".format(
kind, newItem.repr, newSrcInfo.prettyPrint(), oldSrcInfo.prettyPrint()), this)
}
}
......@@ -12,27 +12,40 @@ abstract class Namespace[T <: Identified] {
def all: Iterable[T]
}
class SimpleNamespace[T <: Identified] extends Namespace[T] {
/**
* A simple namespace implementation.
*
* @param kind: a human-readable name for the thing this namespace is supposed to contain.
*/
class SimpleNamespace[T <: Identified](val kind: String = SimpleNamespace.DEFAULT_KIND) extends Namespace[T] {
private type MapType[K, V] = collection.mutable.HashMap[K, V]
private val idMap = new MapType[Int, T]()
private val nameMap = new MapType[String, T]()
def apply(id: Int): T = idMap(id)
def apply(name: String): T = nameMap(name)
@throws(classOf[NoSuchElementException])
def apply(id: Int): T = try {
idMap(id)
} catch {
case e: NoSuchElementException => throw new NoSuchElementException("No %s has ID %d".format(kind, id))
}
@throws(classOf[NoSuchElementException])
def apply(name: String): T = try {
nameMap(name)
} catch {
case e: NoSuchElementException => throw new NoSuchElementException("No %s has name '%s'".format(kind, name))
}
def get(id: Int): Option[T] = idMap.get(id)
def get(name: String): Option[T] = nameMap.get(name)
def add(obj: T) {
for (obj2 <- get(obj.id)) {
throw new NameConflictException(
"Object %s ID-conflicts with %s".format(obj.repr, obj2.repr))
throw new NameConflictException(kind, "id", obj, obj2)
}
for (name <- obj.name; obj2 <- get(name)) {
throw new NameConflictException(
"Object %s name-conflicts with %s".format(obj.repr, obj2.repr))
throw new NameConflictException(kind, "name", obj, obj2)
}
idMap.put(obj.id, obj)
......@@ -45,14 +58,19 @@ class SimpleNamespace[T <: Identified] extends Namespace[T] {
def all = idMap.values
}
class NestedNamespace[T <: Identified](var maybeParent: Option[NestedNamespace[_ >: T <: Identified]]) extends SimpleNamespace[T] {
object SimpleNamespace {
val DEFAULT_KIND = "object"
}
class NestedNamespace[T <: Identified](var maybeParent: Option[NestedNamespace[_ >: T <: Identified]],
kind: String = SimpleNamespace.DEFAULT_KIND) extends SimpleNamespace[T](kind) {
override def add(obj: T): Unit = {
super.add(obj)
maybeParent.foreach(_.add(obj))
}
def makeSubSpace[U <: T](): NestedNamespace[U] = {
new NestedNamespace[U](Some(this))
def makeSubSpace[U <: T](kind: String): NestedNamespace[U] = {
new NestedNamespace[U](Some(this), kind)
}
def reparent[U >: T <: Identified](newParent: NestedNamespace[U]) = {
......@@ -62,3 +80,4 @@ class NestedNamespace[T <: Identified](var maybeParent: Option[NestedNamespace[_
}
}
}
package uvm
/**
* Source information repository.
* <p>
* This class provide information for debugging when things go wrong. For each identified item, this repository records
* source-level information about that item, such as file name, line, column.
*/
class SourceInfoRepo {
private type MapType[K, V] = collection.mutable.HashMap[K, V]
private val dict = new MapType[AnyRef, SourceInfo]()
def apply(obj: AnyRef): SourceInfo = dict.getOrElse(obj, NoSourceInfo)
def update(obj: AnyRef, info: SourceInfo) = dict.update(obj, info)
def merge(that: SourceInfoRepo):Unit = {
for ((k,v) <- dict) {
dict(k) = v
}
}
}
abstract class SourceInfo {
def toString(): String
def prettyPrint(): String
}
object NoSourceInfo extends SourceInfo {
override def toString(): String = "<Source info not available>"
override def prettyPrint(): String = toString()
}
/**
* Location of an item (usually a name, expression, ...) in text-based source code.
* <p>
* @param line the line number, starting from 0
* @param columnStart the first column of the item, starting with 0
* @param columnEnd the last column that contains the item, starting from 0
* @param token the first token (defined by the lexical analyzer) of the item, which roughly identifies where the item
* is even when the exact information is not available.
* @param lineText the whole line of source code at the given line
*/
class TextSourceInfo(val line: Int, val columnStart: Int, val columnEnd: Int, val token: String, val lineText: String) extends SourceInfo {
private def position: String = {
"In %d:%d-%d near '%s'".format(line+1, columnStart+1, columnEnd+1, token)
}
override def toString(): String = position
override def prettyPrint(): String = {
val marker = " " * columnStart + "^" + "~" * (columnEnd - columnStart)
"%s\n%s\n%s".format(position, lineText, marker)
}
}
\ No newline at end of file
package uvm.staticanalysis
class StaticAnalyzer {
}
\ No newline at end of file
......@@ -7,6 +7,7 @@ import org.antlr.v4.runtime.BaseErrorListener
import org.antlr.v4.runtime.tree.TerminalNode
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.Token
import uvm.TextSourceInfo
object AntlrHelpers {
class AccumulativeAntlrErrorListener(source: String) extends BaseErrorListener {
......@@ -25,37 +26,29 @@ object AntlrHelpers {
def getMessages(): String = buf.mkString("\n")
}
def inCtx(ctx: ParserRuleContext, s: String): String = nearTok(ctx.getStart, s)
def inCtx(ctx: TerminalNode, s: String): String = nearTok(ctx.getSymbol, s)
def nearTok(tok: Token, s: String): String = {
val line = tok.getLine()
val column = tok.getCharPositionInLine()
val near = tok.getText()
return "At %d:%d near '%s': %s".format(line, column, near, s)
}
}
trait AdvancedAntlrHelper {
def sourceLines: IndexedSeq[String]
def inCtx(ctx: ParserRuleContext, s: String): String = {
def toSourceInfo(ctx: ParserRuleContext): TextSourceInfo = {
val tok1 = ctx.getStart
val tok2 = ctx.getStop
val firstLine = nearTok(tok1, s)
val line = tok1.getLine() - 1
val theLine = sourceLines(line)
val line = tok1.getLine()
val theLine = sourceLines(line - 1)
val line2 = tok2.getLine()
val line2 = tok2.getLine() - 1
val column = tok1.getCharPositionInLine()
val end = if (line == line2) tok2.getCharPositionInLine else theLine.length()
val end = if (line == line2) (tok2.getCharPositionInLine + tok2.getText.length - 1) else theLine.length()
val near = tok1.getText()
new TextSourceInfo(line, column, end, near, theLine)
}
val marker = " " * column + "^" + "~" * (end - column)
"At %d:%d near '%s': %s\n%s\n%s".format(line, column, near, s, theLine, marker)
def inCtx(ctx: ParserRuleContext, s: String): String = {
val si = toSourceInfo(ctx)
"%s\n%s".format(s, si.prettyPrint())
}
def inCtx(ctx: TerminalNode, s: String): String = nearTok(ctx.getSymbol, s)
......@@ -64,6 +57,6 @@ trait AdvancedAntlrHelper {
val line = tok.getLine()
val column = tok.getCharPositionInLine()
val near = tok.getText()
return "At %d:%d near '%s': %s".format(line, column, near, s)
return "At %s:%d:%d near '%s': %s".format(line, column, near, s)
}
}
\ No newline at end of file
......@@ -13,7 +13,7 @@ class NameSpaceSpec extends FlatSpec with Matchers {
import RichIdentifiedSettable._
it should "remember things by ID and name" in {
val ns = new NestedNamespace[Foo](None)
val ns = new NestedNamespace[Foo](None, "Foo")
val f = new Foo(42) := (1, "f")
ns.add(f)
......@@ -28,9 +28,9 @@ class NameSpaceSpec extends FlatSpec with Matchers {
}
it should "handle nested namespaces" in {
val nsFoo = new NestedNamespace[Foo](None)
val nsBar = nsFoo.makeSubSpace[Bar]()
val nsBaz = nsBar.makeSubSpace[Baz]()
val nsFoo = new NestedNamespace[Foo](None, "Foo")
val nsBar = nsFoo.makeSubSpace[Bar]("Bar")
val nsBaz = nsBar.makeSubSpace[Baz]("Baz")
val f = new Foo(1) := (1, "f")
val g = new Bar(1, 2) := (2, "g")
......
......@@ -27,4 +27,5 @@ class NicerErrorMessage extends FlatSpec with Matchers
e.printStackTrace()
}
}
}
\ No newline at end of file
package uvm.ir.textinput
import org.scalatest.FlatSpec
import org.scalatest.Matchers
import uvm.GlobalBundle
import uvm.TrantientBundle
import uvm.utils.IDFactory
import org.scalatest.exceptions.TestFailedException
class SourceInfoRepoTest extends FlatSpec with Matchers
with TestingBundlesValidators {
def parseText(globalBundle: GlobalBundle, fac: Option[IDFactory]=None)(uir: String): TrantientBundle = {
val idf = fac.getOrElse(new IDFactory(uvm.refimpl.MicroVM.FIRST_CLIENT_USABLE_ID))
val r = new UIRTextReader(idf)
val ir = r.read(new java.io.StringReader(uir), globalBundle)
ir
}
behavior of "SourceInfoRepo"
def catchExceptionWhenParsing(text: String): Unit = {
try {
val gb = new GlobalBundle()
val b = parseText(gb)(text)
fail()
} catch {
case e: TestFailedException => throw e
case e: Exception => // expected
e.printStackTrace()
}
}
it should "give nice error messages for undefined types" in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.typedef @s = struct<@i32>
""")
}
it should "give nice error messages for undefined signatures" in {
catchExceptionWhenParsing("""
.typedef @foo = funcref<@unknown_sig>
""")
}
it should "give nice error messages for undefined constants" in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.typedef @s = struct<@i64>
.const @foo <@s> = {@UNKNOWN_CONST}
""")
}
it should "give nice error messages for undefined functions" in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.const @ZERO <@i64> = 0
.expose @exposed = @unknown_func #DEFAULT @ZERO
""")
}
it should "give nice error messages for undefined basic blocks" in {
catchExceptionWhenParsing("""
.funcsig @f.sig = () -> ()
.funcdef @f VERSION %v1 <@f.sig> {
%entry():
BRANCH %unknown_basic_block()
}
""")
}
it should "give nice error messages for undefined variables" in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.funcsig @f.sig = () -> (@i64)
.funcdef @f VERSION %v1 <@f.sig> {
%entry():
RET %unknwon_var
}
""")
}
it should "produce error when types are re-defined." in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.typedef @i64 = double
""")
}
it should "produce error when function signatures are re-defined." in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.funcsig @foo = () -> ()
.funcsig @foo = (@i64) -> ()
""")
}
it should "produce error when constants are re-defined." in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.const @foo <@i64> = 1
.const @foo <@i64> = 2
""")
}
it should "produce error when global cells are re-defined." in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.global @foo <@i64>
.global @foo <@i64>
""")
}
it should "produce error when exposed functions are re-defined." in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.funcsig @foo = () -> ()
.funcdecl @f <@foo>
.const @ZERO <@i64> = 0
.const @ONE <@i64> = 1
.expose @fe = @f #DEFAULT @ONE
.expose @fe = @f #DEFAULT @ZERO
""")
}
it should "produce error when two functions have the same version name." in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.funcsig @foo = () -> ()
.funcdef @f VERSION @v1 <@foo> {
%entry():
RET ()
}
.funcdef @g VERSION @v1 <@foo> {
%entry():
RET ()
}
""")
}
it should "produce error when two local variables have the same name." in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.funcsig @foo = (@i64 @i64) -> ()
.funcdef @f VERSION %v1 <@foo> {
%entry(<@i64> %a <@i64> %b):
%c = ADD <@i64> %a %b
%c = SUB <@i64> %a %b
RET ()
}
""")
}
it should "produce error when two random things have the same name." in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.funcsig @i64 = () -> ()
""")
}
}
\ No newline at end of file
......@@ -16,7 +16,7 @@ object TestingBundlesValidators {
def anything(s: String) = b.allNs(s)
def ty(s: String) = b.typeNs(s)
def const(s: String) = b.constantNs (s)
def value(s: String) = b.varNs(s)
def value(s: String) = b.globalVarNs(s)
def globalValue(s: String) = b.globalVarNs(s)
def globalCell(s: String) = b.globalCellNs(s)
def sig(s: String) = b.funcSigNs(s)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment