[regex] Clarify exception messages

This commit is contained in:
Ilya Matveev
2018-11-12 20:08:19 +07:00
committed by Ilya Matveev
parent 54beb0c8e8
commit a82dd82402
3 changed files with 71 additions and 38 deletions
@@ -10,5 +10,23 @@ package kotlin.text
* [Pattern]. Might include a detailed description, the original regular
* expression, and the index at which the error occurred.
*/
internal class PatternSyntaxException(val description: String = "", val pattern: String = "", index: Int = -1)
: IllegalArgumentException("Error in \"$pattern\" ($index). $description")
internal class PatternSyntaxException(
val description: String = "",
val pattern: String = "",
val index: Int = -1
) : IllegalArgumentException(formatMessage(description, pattern, index)) {
companion object {
fun formatMessage(description: String, pattern: String, index: Int): String {
if (index < 0 || pattern == "") {
return description
}
val filler = if (index >= 1) " ".repeat(index) else ""
return """
$description near index: $index
$pattern
$filler^
""".trimIndent()
}
}
}
@@ -110,10 +110,14 @@ internal class Lexer(val patternString: String, flags: Int) {
private set
// Indices in the pattern.
private var index = 0 // Current char being processed index.
private var prevNonWhitespaceIndex = 0 // Previous non-whitespace character index.
private var curTokenIndex = 0 // Current token start index.
private var lookAheadTokenIndex = 0 // Next token index.
var index = 0 // Current char being processed index.
private set
var prevNonWhitespaceIndex = 0 // Previous non-whitespace character index.
private set
var curTokenIndex = 0 // Current token start index.
private set
var lookAheadTokenIndex = 0 // Next token index.
private set
init {
var processedPattern = patternString
@@ -391,7 +395,7 @@ internal class Lexer(val patternString: String, flags: Int) {
when (char) {
'!' -> { lookAhead = CHAR_NEG_LOOKBEHIND; nextIndex() }
'=' -> { lookAhead = CHAR_POS_LOOKBEHIND; nextIndex() }
else -> throw PatternSyntaxException()
else -> throw PatternSyntaxException("Unknown look behind", patternString, curTokenIndex)
}
}
} while (isLookBehind)
@@ -427,7 +431,11 @@ internal class Lexer(val patternString: String, flags: Int) {
/** Processes an escaped (\x) character in any mode. Returns whether we need to reread the character or not */
private fun processEscapedChar() : Boolean {
lookAhead = if (index < pattern.size - 2) nextCodePoint() else throw PatternSyntaxException()
lookAhead = if (index < pattern.size - 2) {
nextCodePoint()
} else {
throw PatternSyntaxException("Trailing \\", patternString, curTokenIndex)
}
// The current code point cannot be a surrogate pair because it is an escaped special one.
// Cast it to char or just skip it as if we pass through the else branch of the when below.
@@ -479,8 +487,8 @@ internal class Lexer(val patternString: String, flags: Int) {
// A literal: octal, hex, or hex unicode.
'0' -> lookAhead = readOctals()
'x' -> lookAhead = readHex(2)
'u' -> lookAhead = readHex(4)
'x' -> lookAhead = readHex("hexadecimal", 2)
'u' -> lookAhead = readHex("Unicode", 4)
// Special characters like EOL, EOI etc
'b' -> lookAhead = CHAR_WORD_BOUND
@@ -493,15 +501,15 @@ internal class Lexer(val patternString: String, flags: Int) {
// \cx - A control character corresponding to x.
'c' -> {
if (index < pattern.size - 2) {
//need not care about supplementary codepoints here
// Need not care about supplementary codepoints here.
lookAhead = pattern[nextIndex()].toInt() and 0x1f
} else {
// TODO: Add messages to the exceptions.
throw PatternSyntaxException()
throw PatternSyntaxException("Illegal control sequence", patternString, curTokenIndex)
}
}
'C', 'E', 'F', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'R', 'T', 'U', 'V', 'X', 'Y', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'o', 'q', 'y' -> throw PatternSyntaxException()
'C', 'E', 'F', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'R', 'T', 'U', 'V', 'X', 'Y', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'o', 'q', 'y' ->
throw PatternSyntaxException("Illegal escape sequence", patternString, curTokenIndex)
}
return false
}
@@ -514,16 +522,20 @@ internal class Lexer(val patternString: String, flags: Int) {
var max = -1
// Obtain a min value.
var char: Char = if (index < pattern.size) pattern[nextIndex()] else throw PatternSyntaxException()
var char: Char = if (index < pattern.size) {
pattern[nextIndex()]
} else {
throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
}
while (char != '}') {
if (char == ',' && min < 0) {
try {
val minParsed = sb.toString().toInt()
min = if (minParsed >= 0) minParsed else throw PatternSyntaxException()
min = if (minParsed >= 0) minParsed else throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
sb.setLength(0)
} catch (nfe: NumberFormatException) {
throw PatternSyntaxException()
throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
}
} else {
sb.append(char)
@@ -532,24 +544,24 @@ internal class Lexer(val patternString: String, flags: Int) {
}
if (char != '}') {
throw PatternSyntaxException()
throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
}
// Obtain a max value, if it exists
if (sb.isNotEmpty()) {
try {
val maxParsed = sb.toString().toInt()
max = if (maxParsed >= 0) maxParsed else throw PatternSyntaxException()
max = if (maxParsed >= 0) maxParsed else throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
if (min < 0) {
min = max
}
} catch (nfe: NumberFormatException) {
throw PatternSyntaxException()
throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
}
}
if (min < 0 || max >=0 && max < min) {
throw PatternSyntaxException()
throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
}
val mod = if (index < pattern.size) pattern[index] else '*'
@@ -572,7 +584,7 @@ internal class Lexer(val patternString: String, flags: Int) {
when (char) {
'-' -> {
if (!positive) {
throw PatternSyntaxException()
throw PatternSyntaxException("Illegal inline construct", patternString, curTokenIndex)
}
positive = false
}
@@ -620,7 +632,7 @@ internal class Lexer(val patternString: String, flags: Int) {
}
nextIndex()
}
throw PatternSyntaxException()
throw PatternSyntaxException("Illegal inline construct", patternString, curTokenIndex)
}
/** Parse character classes names and verifies correction of the syntax */
@@ -638,9 +650,9 @@ internal class Lexer(val patternString: String, flags: Int) {
sb.append(char)
char = pattern[nextIndex()]
}
if (char != '}') throw PatternSyntaxException()
if (char != '}') throw PatternSyntaxException("Unclosed character family", patternString, curTokenIndex)
}
if (sb.isEmpty()) throw PatternSyntaxException()
if (sb.isEmpty()) throw PatternSyntaxException("Empty character family", patternString, curTokenIndex)
val res = sb.toString()
return when {
@@ -651,7 +663,7 @@ internal class Lexer(val patternString: String, flags: Int) {
}
/** Process hexadecimal integer. */
private fun readHex(max: Int): Int {
private fun readHex(radixName: String, max: Int): Int {
val builder = StringBuilder(max)
val length = pattern.size - 2
var i = 0
@@ -664,7 +676,7 @@ internal class Lexer(val patternString: String, flags: Int) {
return builder.toString().toInt(16)
} catch (e: NumberFormatException) {}
}
throw PatternSyntaxException()
throw PatternSyntaxException("Invalid $radixName escape sequence", patternString, curTokenIndex)
}
/** Process octal integer. */
@@ -673,7 +685,7 @@ internal class Lexer(val patternString: String, flags: Int) {
var result = 0
var digit = digitOf(pattern[index], 8)
if (digit == -1) {
throw PatternSyntaxException()
throw PatternSyntaxException("Invalid octal escape sequence", patternString, curTokenIndex)
}
val max = if (digit > 3) 2 else 3
var i = 0
@@ -63,7 +63,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
startNode = processExpression(-1, this.flags, null)
if (!lexemes.isEmpty()) {
throw PatternSyntaxException()
throw PatternSyntaxException("Trailing characters", pattern, lexemes.curTokenIndex)
}
// Finalize compilation
@@ -309,7 +309,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
}
lexemes.currentChar == Lexer.CHAR_RIGHT_PARENTHESIS -> {
if (last is FinalSet) {
throw PatternSyntaxException()
throw PatternSyntaxException("unmatched )", pattern, lexemes.curTokenIndex)
}
cur = EmptySet(last)
}
@@ -472,7 +472,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
}
term = processExpression(char and 0xff00ffff.toInt(), newFlags, last) // Remove flags from the token.
if (lexemes.currentChar != Lexer.CHAR_RIGHT_PARENTHESIS) {
throw PatternSyntaxException()
throw PatternSyntaxException("unmatched (", pattern, lexemes.curTokenIndex)
}
lexemes.next()
} else {
@@ -488,7 +488,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
term = processRange(negative, last)
if (lexemes.currentChar != Lexer.CHAR_RIGHT_SQUARE_BRACKET) {
throw PatternSyntaxException()
throw PatternSyntaxException("unmatched [", pattern, lexemes.curTokenIndex)
}
lexemes.setModeWithReread(Lexer.Mode.PATTERN)
lexemes.next()
@@ -560,7 +560,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
backRefs[number]!!.isBackReferenced = true
needsBackRefReplacement = true // And process back references in the second pass.
} else {
throw PatternSyntaxException() // Wrong group number.
throw PatternSyntaxException("No such group yet exists at this point in the pattern", pattern, lexemes.curTokenIndex)
}
}
@@ -592,11 +592,14 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
}
char == Lexer.CHAR_RIGHT_PARENTHESIS -> {
if (last is FinalSet) {
throw PatternSyntaxException()
throw PatternSyntaxException("unmatched )", pattern, lexemes.curTokenIndex)
}
term = EmptySet(last)
}
else -> throw PatternSyntaxException()
else -> {
val current = if (lexemes.isSpecial) lexemes.curSpecialToken.toString() else char.toString()
throw PatternSyntaxException("Dangling meta construction: $current", pattern, lexemes.curTokenIndex)
}
}
}
}
@@ -715,13 +718,13 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
}
result.add(buffer, cur)
} catch (e: Exception) {
throw PatternSyntaxException()
throw PatternSyntaxException("Illegal character range", pattern, lexemes.curTokenIndex)
}
lexemes.next()
buffer = -1
} else {
throw PatternSyntaxException()
throw PatternSyntaxException("Illegal character range", pattern, lexemes.curTokenIndex)
}
}
}
@@ -761,7 +764,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
notClosed = lexemes.currentChar != Lexer.CHAR_RIGHT_SQUARE_BRACKET
}
if (notClosed) {
throw PatternSyntaxException()
throw PatternSyntaxException("Missing ']'", pattern, lexemes.curTokenIndex)
}
if (buffer >= 0) {
result.add(buffer)