Skip to content

Commit

Permalink
Use printable strings when rendering errors
Browse files Browse the repository at this point in the history
  • Loading branch information
alllex committed Dec 19, 2023
1 parent efc71f2 commit d068e10
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 16 deletions.
22 changes: 15 additions & 7 deletions src/commonMain/kotlin/me/alllex/parsus/parser/ParseResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package me.alllex.parsus.parser
import me.alllex.parsus.token.Token
import me.alllex.parsus.token.TokenMatch
import me.alllex.parsus.util.replaceNonPrintable
import me.alllex.parsus.util.toPrintableString

/**
* Result of a parse that is either a [parsed value][ParsedValue]
Expand Down Expand Up @@ -36,18 +37,21 @@ abstract class ParseError : ParseResult<Nothing>() {
appendLine()
append(" ".repeat(lookBehind)).append(messageAtOffset)
appendLine()
append(" ".repeat(lookBehind)).append("| offset=$offset (or after ignored tokens)")
append(" ".repeat(lookBehind)).append("$arrowDown offset=$offset (or after ignored tokens)")
appendLine()
appendLine(replaceNonPrintable(inputSection))
if (previousTokenMatch != null) {
append("^".repeat(previousTokenMatch.length.coerceAtLeast(1)))
append(arrowUp.repeat(previousTokenMatch.length.coerceAtLeast(1)))
append(" Previous token: ${previousTokenMatch.token} at offset=${previousTokenMatch.offset}")
appendLine()
}
}
}
}

private const val arrowDown = "\u2193"
private const val arrowUp = "\u2191"

data class ParseErrorContext(
val inputSection: String,
val lookBehind: Int,
Expand All @@ -68,8 +72,8 @@ data class UnmatchedToken(
override fun toString(): String = describe()

override fun describe(): String = format(
message = "Unmatched token at offset=$offset, when expected: $expected",
messageAtOffset = "Expected token: $expected"
message = "Unmatched token at offset=$offset, when expected: ${expected.toPrintableString()}",
messageAtOffset = "Expected token: ${expected.toPrintableString()}"
)
}

Expand All @@ -81,8 +85,8 @@ data class MismatchedToken(
override val offset: Int get() = found.offset
override fun toString(): String = describe()
override fun describe(): String = format(
message = "Mismatched token at offset=$offset, when expected: $expected, got: ${found.token}",
messageAtOffset = "Expected token: $expected at offset=$offset, got: ${found.token}"
message = "Mismatched token at offset=$offset, when expected: ${expected.toPrintableString()}, got: ${found.token.toPrintableString()}",
messageAtOffset = "Expected token: ${expected.toPrintableString()} at offset=$offset, got: ${found.token.toPrintableString()}"
)
}

Expand All @@ -107,7 +111,11 @@ data class NoViableAlternative(
)
}

data class NotEnoughRepetition(override val offset: Int, val expectedAtLeast: Int, val actualCount: Int) : ParseError() {
data class NotEnoughRepetition(
override val offset: Int,
val expectedAtLeast: Int,
val actualCount: Int
) : ParseError() {
override fun toString(): String = describe()
override fun describe(): String = "Expected at least $expectedAtLeast, found $actualCount"
}
Expand Down
2 changes: 2 additions & 0 deletions src/commonMain/kotlin/me/alllex/parsus/util/text.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package me.alllex.parsus.util

internal fun Any.toPrintableString() = replaceNonPrintable(toString())

internal fun replaceNonPrintable(string: String): String {
return buildString {
for (char in string) {
Expand Down
18 changes: 9 additions & 9 deletions src/commonTest/kotlin/me/alllex/parsus/ParseErrorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,26 @@ class ParseErrorTest {
assertNotParsed("abab").prop(ParseError::describe).isEqualTo(
"Unmatched token at offset=2, when expected: LiteralToken('cd')\n" + """
Expected token: LiteralToken('cd')
| offset=2 (or after ignored tokens)
offset=2 (or after ignored tokens)
abab
^^ Previous token: LiteralToken('ab') at offset=0
↑↑ Previous token: LiteralToken('ab') at offset=0
""".trimIndent() + "\n"
)

assertNotParsed("cd").prop(ParseError::describe).isEqualTo(
"Unmatched token at offset=0, when expected: LiteralToken('ab')\n" + """
Expected token: LiteralToken('ab')
| offset=0 (or after ignored tokens)
offset=0 (or after ignored tokens)
cd
""".trimIndent() + "\n"
)

assertNotParsed("abcdab").prop(ParseError::describe).isEqualTo(
"Unmatched token at offset=4, when expected: Token(EOF)\n" + """
Expected token: Token(EOF)
| offset=4 (or after ignored tokens)
offset=4 (or after ignored tokens)
cdab
^^ Previous token: LiteralToken('cd') at offset=2
↑↑ Previous token: LiteralToken('cd') at offset=2
""".trimIndent() + "\n"
)
}
Expand All @@ -63,9 +63,9 @@ class ParseErrorTest {
assertNotParsed("ab ab").prop(ParseError::describe).isEqualTo(
"Unmatched token at offset=2, when expected: LiteralToken('cd')\n" + """
Expected token: LiteralToken('cd')
| offset=2 (or after ignored tokens)
offset=2 (or after ignored tokens)
ab␣ab
^^ Previous token: LiteralToken('ab') at offset=0
↑↑ Previous token: LiteralToken('ab') at offset=0
""".trimIndent() + "\n"
)
}
Expand All @@ -85,9 +85,9 @@ class ParseErrorTest {
assertNotParsed(" \t\r\ncd").prop(ParseError::describe).isEqualTo(
"Unmatched token at offset=4, when expected: LiteralToken('ab')\n" + """
Expected token: LiteralToken('ab')
| offset=4 (or after ignored tokens)
offset=4 (or after ignored tokens)
␣␉␍␤cd
^^^^ Previous token: RegexToken(ws [\s+]) at offset=0
↑↑↑↑ Previous token: RegexToken(ws [\s+]) at offset=0
""".trimIndent() + "\n"
)
}
Expand Down

0 comments on commit d068e10

Please sign in to comment.