From ecaa7cbfab5cbe77650d6c021be5e59f35db7565 Mon Sep 17 00:00:00 2001 From: Edward Kmett Date: Fri, 1 Nov 2019 22:22:22 -0700 Subject: [PATCH] split out diagnostics to reduce dependecy on truffle for org.intelligence.* --- src/asm/types.kt | 2 +- src/cadenza/language.kt | 11 +- src/cadenza/launcher.kt | 5 +- src/cadenza/panic.kt | 22 ++-- src/diagnostics.kt | 48 +++++++++ src/parser.kt | 227 ++++++++++++++++++---------------------- src/pretty.kt | 13 +-- 7 files changed, 180 insertions(+), 148 deletions(-) create mode 100644 src/diagnostics.kt diff --git a/src/asm/types.kt b/src/asm/types.kt index 2c26433..6e845c1 100644 --- a/src/asm/types.kt +++ b/src/asm/types.kt @@ -16,7 +16,7 @@ val double: Type get() = Type.DOUBLE_TYPE val boolean: Type get() = Type.BOOLEAN_TYPE fun type(k: KClass<*>): Type = Type.getType(k.java) fun type(t: String): Type = Type.getObjectType(t) -val Type.array: Type get() = Type.getType("[${descriptor}") +val Type.array: Type get() = Type.getType("[$descriptor") val string: Type get() = +String::class val `object`: Type get() = +Object::class val `class`: Type get() = +Class::class diff --git a/src/cadenza/language.kt b/src/cadenza/language.kt index 434b8e0..147506e 100644 --- a/src/cadenza/language.kt +++ b/src/cadenza/language.kt @@ -1,12 +1,13 @@ package cadenza -import cadenza.semantics.Type.* import cadenza.data.Closure import cadenza.jit.Code import cadenza.jit.InlineCode import cadenza.jit.ProgramRootNode import cadenza.semantics.Term import cadenza.semantics.Type +import cadenza.semantics.Type.Arr +import cadenza.semantics.Type.Nat import com.oracle.truffle.api.* import com.oracle.truffle.api.TruffleLanguage.ContextPolicy import com.oracle.truffle.api.debug.DebuggerTags @@ -18,7 +19,8 @@ import com.oracle.truffle.api.interop.TruffleObject import com.oracle.truffle.api.interop.UnsupportedMessageException import com.oracle.truffle.api.nodes.NodeInfo import com.oracle.truffle.api.source.SourceSection -import org.graalvm.options.* +import org.graalvm.options.OptionDescriptors +import org.graalvm.options.OptionValues import org.graalvm.polyglot.Source import java.io.IOException import java.nio.charset.Charset @@ -120,9 +122,6 @@ class Language : TruffleLanguage() { fun shutdown() {} } - - - private val singleContextAssumption = Truffle.getRuntime().createAssumption("Only a single context is active")!! override fun createContext(env: Env) = Context(this, env) override fun initializeContext(ctx: Context?) {} @@ -153,7 +152,7 @@ class Language : TruffleLanguage() { // stubbed: returns a calculation that adds two numbers override fun parse(request: ParsingRequest): CallTarget { val rootNode = ProgramRootNode(this, Code.LitInt(0), FrameDescriptor()) - //panic("at the disco") + // todo return Truffle.getRuntime().createCallTarget(rootNode) } diff --git a/src/cadenza/launcher.kt b/src/cadenza/launcher.kt index 7975d9f..825e0a7 100644 --- a/src/cadenza/launcher.kt +++ b/src/cadenza/launcher.kt @@ -34,8 +34,9 @@ internal fun PolyglotException.prettyStackTrace(trim: Boolean = true) { } } val out = ansi() - if (isHostException) out.fgRed().a(asHostException().toString()) - else out.fgBrightYellow().a(message) + //if (isHostException) out.fgRed().a(asHostException().toString()) + //else out.fgBrightYellow().a(message) + out.a(if (isHostException) asHostException().toString() else message) out.reset().a('\n').fgBrightBlack() stackTrace.forEach { out.a(Attribute.ITALIC).a(" at ").a(Attribute.ITALIC_OFF).a(it).a('\n') diff --git a/src/cadenza/panic.kt b/src/cadenza/panic.kt index d0d154a..ba4927b 100644 --- a/src/cadenza/panic.kt +++ b/src/cadenza/panic.kt @@ -1,6 +1,9 @@ package cadenza import com.oracle.truffle.api.CompilerDirectives +import org.intelligence.diagnostics.Severity +import org.intelligence.diagnostics.error +import org.intelligence.pretty.Pretty private inline fun Array.trim(i : Int = 1): Array = this.drop(i).toTypedArray() @@ -9,7 +12,11 @@ internal class Panic(message: String? = null) : RuntimeException(message) { initCause(cause) } companion object { const val serialVersionUID : Long = 1L } - override fun toString(): String = if (message.isNullOrEmpty()) "panic" else "panic: $message" + override fun toString(): String = stackTrace?.getOrNull(0)?.let { + Pretty.ppString { + error(Severity.panic, it.fileName, it.lineNumber, null, null, message) + } + } ?: super.toString() } fun panic(msg: String, base: Throwable?): Nothing { @@ -23,15 +30,18 @@ fun panic(msg: String): Nothing { throw Panic(msg).also { it.stackTrace = it.stackTrace.trim() } } -internal class TODO private constructor() : RuntimeException() { +internal class TODO() : RuntimeException() { companion object { const val serialVersionUID : Long = 1L } - override fun toString(): String = stackTrace[0].let { - "${it.fileName}:${it.lineNumber}: todo (${it.methodName})" - } + override fun toString(): String = + stackTrace?.getOrNull(0)?.let { + Pretty.ppString { + error(Severity.todo, it.fileName, it.lineNumber, null, null, it.methodName) + } + } ?: super.toString() } val todo: Nothing get() { CompilerDirectives.transferToInterpreter() - throw RuntimeException().also { it.stackTrace = it.stackTrace.trim() } + throw TODO().also { it.stackTrace = it.stackTrace.trim() } } diff --git a/src/diagnostics.kt b/src/diagnostics.kt new file mode 100644 index 0000000..d380441 --- /dev/null +++ b/src/diagnostics.kt @@ -0,0 +1,48 @@ +package org.intelligence.diagnostics + +import com.oracle.truffle.api.source.Source +import org.fusesource.jansi.Ansi +import org.intelligence.pretty.* + +enum class Severity { info, warning, error, todo, panic } + +fun Pretty.error(severity: Severity = Severity.error, file: String, line: Int, col: Int? = null, sourceLine: CharSequence? = null, message: String? = null, vararg expected: Any) { + bold { + text(file); char(':'); simple(line); char(':'); + col?.also { simple(it); char(':') }; + space + when (severity) { + Severity.error -> red { text("error:") } + Severity.panic -> fg(Ansi.Color.RED, true) { text("panic:") } + Severity.warning -> magenta { text("warning:") } + Severity.info -> blue { text("info:") } + Severity.todo -> yellow { text("todo:")} + } + space + nest(2) { + if (expected.isEmpty()) text(message ?: "expected nothing") + else { + if (message != null) { + text(message);text(",");space + } + text("expected") + space + oxfordBy(by = Pretty::simple, conjunction = "or", docs = *expected) + } + } + } + sourceLine?.also { + hardLine + text(it) + col?.also { nest(it - 1) { newline; cyan { char('^') } } } + } +} + +// convenient pretty printer +fun Pretty.error(severity: Severity = Severity.error, source: Source, pos: Int, message: String? = null, vararg expected: Any) { + val line = source.getLineNumber(pos) + val ls = source.getLineStartOffset(line) + val ll = source.getLineLength(line) + val sourceText = source.characters.subSequence(ls, ls+ll) + error(severity, source.name, line, source.getColumnNumber(pos), sourceText, message, *expected) +} \ No newline at end of file diff --git a/src/parser.kt b/src/parser.kt index b1f950d..9e89d5b 100644 --- a/src/parser.kt +++ b/src/parser.kt @@ -1,18 +1,35 @@ package org.intelligence.parser -import org.intelligence.pretty.* import com.oracle.truffle.api.source.Source +import org.intelligence.diagnostics.Severity +import org.intelligence.diagnostics.error +import org.intelligence.pretty.Pretty import java.util.regex.Matcher import java.util.regex.Pattern -class ParseError(var pos: Int, message: String? = null) : Exception(message) { - override fun fillInStackTrace() = this // don't record - companion object { const val serialVersionUID : Long = 1L } -} +class Parse(val source: Source) { + class Error(var pos: Int, message: String? = null) : Exception(message) { + override fun fillInStackTrace() = this // don't record + companion object { const val serialVersionUID : Long = 1L } + } -data class Expected(val what: Any, val next: Expected?) + data class Expected(val what: Any, val next: Expected?) + + val characters: CharSequence = source.characters + var expects: Expected? = null // var is intended, we swap it out regularly + + var pos: Int = 0 + set(value) { + if (field != value) { + field = value + expects = null + } + } + + val expected: List get() = expects.toList() +} -fun Expected?.toList() : List { +fun Parse.Expected?.toList() : List { val out = mutableListOf() var current = this while (current != null) { @@ -22,65 +39,70 @@ fun Expected?.toList() : List { return out } -class ParseState(val source: Source) { - val characters: CharSequence = source.characters - var expected: Expected? = null // var is intended, we swap it out regularly - var pos: Int = 0 - set(value) { - if (field != value) { - field = value - expected = null - } - } +// these would nested classes but for KT-1395 screwing up my namespaces +sealed class ParseResult +data class ParseSuccess(val value: T): ParseResult() +data class ParseFailure( + val source: Source, + val pos: Int, + val message: String? = null, + val expected: List = emptyList() +): ParseResult() { + val line: Int get() = source.getLineNumber(pos) + val col: Int get() = source.getColumnNumber(pos) + val loc : String get() = "${source.name}:$line:$col" + override fun toString(): String = Pretty.ppString { + error(Severity.error, source, pos, message, *expected.toTypedArray()) + } } -val ParseState.name: String get() = source.name -val ParseState.path: String get() = source.path -val ParseState.line: Int get() = source.getLineNumber(pos) -val ParseState.col: Int get() = source.getColumnNumber(pos) +val Parse.name: String get() = source.name +val Parse.path: String get() = source.path +val Parse.line: Int get() = source.getLineNumber(pos) +val Parse.col: Int get() = source.getColumnNumber(pos) -// parsers consume parsestate as this and throw ParseErrors on failure -typealias Parser = ParseState.() -> T +// parsers consume parsestate as this and throw Parsing.Errors on failure +typealias Parser = Parse.() -> T // mark/release support inline class Mark(val pos: Int) -val ParseState.mark: Mark get() = Mark(pos) -fun ParseState.release(mark: Mark) { pos = mark.pos } +val Parse.mark: Mark get() = Mark(pos) +fun Parse.release(mark: Mark) { pos = mark.pos } -@Throws(ParseError::class) -fun ParseState.fail(message: String? = null): Nothing = throw ParseError(pos, message) +@Throws(Parse.Error::class) +fun Parse.fail(message: String? = null): Nothing = throw Parse.Error(pos, message) -@Throws(ParseError::class) -fun ParseState.expected(what: Any): Nothing { - expected = Expected(what, expected) - throw ParseError(pos) +@Throws(Parse.Error::class) +fun Parse.expected(what: Any): Nothing { + expects = Parse.Expected(what, expects) + throw Parse.Error(pos) } fun parser(p: Parser): Parser = p -@Throws(ParseError::class) +@Throws(Parse.Error::class) fun expected2(what: Any): Parser = parser { - expected = Expected(what, expected) - throw ParseError(pos) + expects = Parse.Expected(what, expects) + throw Parse.Error(pos) } -val ParseState.eof: Unit - @Throws(ParseError::class) +val Parse.eof: Unit + @Throws(Parse.Error::class) get() { if (!atEof) expected("EOF") } -val ParseState.atEof: Boolean get() = pos == characters.length +val Parse.atEof: Boolean get() = pos == characters.length -@Throws(ParseError::class) -fun ParseState.char(c: Char): Char { +@Throws(Parse.Error::class) +fun Parse.char(c: Char): Char { if (pos >= characters.length || c != characters[pos]) expected(c) ++pos return c } -@Throws(ParseError::class) -fun ParseState.satisfy(predicate : (Char) -> Boolean): Char { +@Throws(Parse.Error::class) +fun Parse.satisfy(predicate : (Char) -> Boolean): Char { if (pos >= characters.length) fail() val c = characters[pos] if (!predicate(c)) fail() @@ -88,36 +110,36 @@ fun ParseState.satisfy(predicate : (Char) -> Boolean): Char { return c } -val ParseState.next: Char - @Throws(ParseError::class) +val Parse.next: Char + @Throws(Parse.Error::class) get() { if (pos >= characters.length) expected("any character") return characters[pos++] } -inline fun ParseState.trying(what: Any, action: Parser): T = pos.let { - val oldExpected = expected - expected = null +inline fun Parse.trying(what: Any, action: Parser): T = pos.let { + val oldExpected = expects + expects = null try { action(this) } - catch (e: ParseError) { + catch (e: Parse.Error) { pos = it - expected = Expected(what, oldExpected) - throw ParseError(it) // with no expectations or message + expects = Parse.Expected(what, oldExpected) + throw Parse.Error(it) // with no expectations or message } } -fun ParseState.named(what: Any, action: Parser): T = pos.let { - val oldExpected = expected - expected = null +fun Parse.named(what: Any, action: Parser): T = pos.let { + val oldExpected = expects + expects = null try { return action(this) - } catch (e: ParseError) { - if (e.pos == it) expected = Expected(what, oldExpected) + } catch (e: Parse.Error) { + if (e.pos == it) expects = Parse.Expected(what, oldExpected) throw e } } -@Throws(ParseError::class) -fun ParseState.match(pattern: Pattern) : Matcher = +@Throws(Parse.Error::class) +fun Parse.match(pattern: Pattern) : Matcher = pattern.matcher(characters).also { it.useTransparentBounds(true) it.region(pos,characters.length) @@ -126,63 +148,63 @@ fun ParseState.match(pattern: Pattern) : Matcher = } @Suppress("NOTHING_TO_INLINE") // really? -inline fun ParseState.choice(vararg alts: Parser): T { +inline fun Parse.choice(vararg alts: Parser): T { val old = pos alts.forEach { try { return it(this) - } catch (e: ParseError) { + } catch (e: Parse.Error) { if (e.pos != old) throw e // position didn't change, try next alternative } } - throw ParseError(old) + throw Parse.Error(old) } -@Throws(ParseError::class) -inline fun ParseState.many(item: Parser): List { +@Throws(Parse.Error::class) +inline fun Parse.many(item: Parser): List { val result = mutableListOf() while (true) { val old = pos try { result.add(item(this)) - } catch (e: ParseError) { + } catch (e: Parse.Error) { if (e.pos == old) return result // failed without consuming, success throw e } } } -@Throws(ParseError::class) -inline fun ParseState.some(item: Parser): List { +@Throws(Parse.Error::class) +inline fun Parse.some(item: Parser): List { val result = mutableListOf() result.add(item(this)) while (true) { val old = pos try { result.add(item(this)) - } catch (e: ParseError) { + } catch (e: Parse.Error) { if (e.pos == old) return result throw e } } } -@Throws(ParseError::class) -inline fun ParseState.optional(item: Parser): T? { +@Throws(Parse.Error::class) +inline fun Parse.optional(item: Parser): T? { val old = pos return try { item(this) - } catch (e: ParseError) { + } catch (e: Parse.Error) { if (pos == old) null else throw e } } -@Throws(ParseError::class) -fun ParseState.parse(p: Parser): T = p(this) +@Throws(Parse.Error::class) +fun Parse.parse(p: Parser): T = p(this) -@Throws(ParseError::class) -inline fun ParseState.manyTillPair(p: Parser, q: Parser): Pair,B> { +@Throws(Parse.Error::class) +inline fun Parse.manyTillPair(p: Parser, q: Parser): Pair,B> { val result = mutableListOf() while (true) { val last = optional(q) @@ -191,8 +213,8 @@ inline fun ParseState.manyTillPair(p: Parser, q: Parser): Pair ParseState.manyTill(p: Parser, q: Parser<*>): List { +@Throws(Parse.Error::class) +inline fun Parse.manyTill(p: Parser, q: Parser<*>): List { val result = mutableListOf() while (true) { val last = optional(q) @@ -201,60 +223,11 @@ inline fun ParseState.manyTill(p: Parser, q: Parser<*>): List { } } -sealed class ParseResult -data class Success(val value: T): ParseResult() - -enum class Severity { info, warning, error } - -// convenient pretty printer -fun Pretty.error(source: Source, severity: Severity = Severity.error, pos: Int, message: String? = null, vararg expected: Any) { - val l = source.getLineNumber(pos) - val c = source.getColumnNumber(pos) - bold { - text(source.name); char(':'); simple(l); char(':'); simple(c); space - when (severity) { - Severity.error -> red { text("error:") } - Severity.warning -> magenta { text("warning:") } - Severity.info -> blue { text("info:") } - } - space - nest(2) { - if (expected.isEmpty()) text(message ?: "expected nothing") - else { - if (message != null) { - text(message);text(",");space - } - text("expected") - space - oxfordBy(by = Pretty::simple, conjunction = "or", docs = *expected) - } - } - } - hardLine - val ls = source.getLineStartOffset(l) - val ll = source.getLineLength(l) - text(source.characters.subSequence(ls, ls+ll)) - nest(c-1) { newline; cyan { char('^') } } - hardLine -} - -data class Failure( - val source: Source, - val pos: Int, - val message: String? = null, - val expected: List = emptyList() -): ParseResult() { - val line: Int get() = source.getLineNumber(pos) - val col: Int get() = source.getColumnNumber(pos) - val loc : String get() = "${source.name}:$line:$col" - override fun toString(): String = Pretty.ppString { error(source, Severity.error, pos, message, *expected.toTypedArray()) } -} - fun Source.parse(parser: Parser) : ParseResult = - ParseState(this).let { + Parse(this).let { try { - Success(parser(it)) - } catch (e: ParseError) { - Failure(this, it.pos, e.message, it.expected.toList()) + ParseSuccess(parser(it)) + } catch (e: Parse.Error) { + ParseFailure(this, it.pos, e.message, it.expects.toList()) } } \ No newline at end of file diff --git a/src/pretty.kt b/src/pretty.kt index 6c8bebf..12b5f32 100644 --- a/src/pretty.kt +++ b/src/pretty.kt @@ -1,6 +1,6 @@ package org.intelligence.pretty -import org.fusesource.jansi.* +import org.fusesource.jansi.Ansi // assumptions typealias W = Int // characters @@ -107,8 +107,8 @@ class Pretty( ) { fun tell(a: Out) = output.add(a) - internal class Fail : RuntimeException() { override fun fillInStackTrace() = this } - internal val fail: Nothing get() { throw Fail() } + internal object Bad : RuntimeException() { override fun fillInStackTrace() = this } + internal val bad: Nothing get() { assert(canFail); throw Bad } companion object { const val DEFAULT_MAX_WIDTH: Int = 80 @@ -140,7 +140,7 @@ class Pretty( val newline: Unit get() { hardLine; space(nesting) } internal fun chunk(c: Chunk) { val newLineLen = curLineLen + c.len() - if (canFail && (nesting + newLineLen > maxWidth || newLineLen > maxRibbon)) fail + if (canFail && (nesting + newLineLen > maxWidth || newLineLen > maxRibbon)) bad tell(c) curLineLen = newLineLen } @@ -163,7 +163,7 @@ inline fun Pretty.grouped(body: D): A { isFlat = true try { return body(this) - } catch(e: Pretty.Fail) { + } catch(e: Pretty.Bad) { } finally { canFail = oldCanFail isFlat = false @@ -383,4 +383,5 @@ fun Pretty.italic(f: D): A = override fun reset(ansi: Ansi) { ansi.a(Ansi.Attribute.ITALIC_OFF) } } annotate(ann, f) - } \ No newline at end of file + } +