[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 * [Pattern]. Might include a detailed description, the original regular
* expression, and the index at which the error occurred. * expression, and the index at which the error occurred.
*/ */
internal class PatternSyntaxException(val description: String = "", val pattern: String = "", index: Int = -1) internal class PatternSyntaxException(
: IllegalArgumentException("Error in \"$pattern\" ($index). $description") 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 private set
// Indices in the pattern. // Indices in the pattern.
private var index = 0 // Current char being processed index. var index = 0 // Current char being processed index.
private var prevNonWhitespaceIndex = 0 // Previous non-whitespace character index. private set
private var curTokenIndex = 0 // Current token start index. var prevNonWhitespaceIndex = 0 // Previous non-whitespace character index.
private var lookAheadTokenIndex = 0 // Next token index. private set
var curTokenIndex = 0 // Current token start index.
private set
var lookAheadTokenIndex = 0 // Next token index.
private set
init { init {
var processedPattern = patternString var processedPattern = patternString
@@ -391,7 +395,7 @@ internal class Lexer(val patternString: String, flags: Int) {
when (char) { when (char) {
'!' -> { lookAhead = CHAR_NEG_LOOKBEHIND; nextIndex() } '!' -> { lookAhead = CHAR_NEG_LOOKBEHIND; nextIndex() }
'=' -> { lookAhead = CHAR_POS_LOOKBEHIND; nextIndex() } '=' -> { lookAhead = CHAR_POS_LOOKBEHIND; nextIndex() }
else -> throw PatternSyntaxException() else -> throw PatternSyntaxException("Unknown look behind", patternString, curTokenIndex)
} }
} }
} while (isLookBehind) } 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 */ /** Processes an escaped (\x) character in any mode. Returns whether we need to reread the character or not */
private fun processEscapedChar() : Boolean { 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. // 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. // 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. // A literal: octal, hex, or hex unicode.
'0' -> lookAhead = readOctals() '0' -> lookAhead = readOctals()
'x' -> lookAhead = readHex(2) 'x' -> lookAhead = readHex("hexadecimal", 2)
'u' -> lookAhead = readHex(4) 'u' -> lookAhead = readHex("Unicode", 4)
// Special characters like EOL, EOI etc // Special characters like EOL, EOI etc
'b' -> lookAhead = CHAR_WORD_BOUND 'b' -> lookAhead = CHAR_WORD_BOUND
@@ -493,15 +501,15 @@ internal class Lexer(val patternString: String, flags: Int) {
// \cx - A control character corresponding to x. // \cx - A control character corresponding to x.
'c' -> { 'c' -> {
if (index < pattern.size - 2) { 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 lookAhead = pattern[nextIndex()].toInt() and 0x1f
} else { } else {
// TODO: Add messages to the exceptions. throw PatternSyntaxException("Illegal control sequence", patternString, curTokenIndex)
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() '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 return false
} }
@@ -514,16 +522,20 @@ internal class Lexer(val patternString: String, flags: Int) {
var max = -1 var max = -1
// Obtain a min value. // 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 != '}') { while (char != '}') {
if (char == ',' && min < 0) { if (char == ',' && min < 0) {
try { try {
val minParsed = sb.toString().toInt() 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) sb.setLength(0)
} catch (nfe: NumberFormatException) { } catch (nfe: NumberFormatException) {
throw PatternSyntaxException() throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
} }
} else { } else {
sb.append(char) sb.append(char)
@@ -532,24 +544,24 @@ internal class Lexer(val patternString: String, flags: Int) {
} }
if (char != '}') { if (char != '}') {
throw PatternSyntaxException() throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
} }
// Obtain a max value, if it exists // Obtain a max value, if it exists
if (sb.isNotEmpty()) { if (sb.isNotEmpty()) {
try { try {
val maxParsed = sb.toString().toInt() 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) { if (min < 0) {
min = max min = max
} }
} catch (nfe: NumberFormatException) { } catch (nfe: NumberFormatException) {
throw PatternSyntaxException() throw PatternSyntaxException("Incorrect Quantifier Syntax", patternString, curTokenIndex)
} }
} }
if (min < 0 || max >=0 && max < min) { 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 '*' val mod = if (index < pattern.size) pattern[index] else '*'
@@ -572,7 +584,7 @@ internal class Lexer(val patternString: String, flags: Int) {
when (char) { when (char) {
'-' -> { '-' -> {
if (!positive) { if (!positive) {
throw PatternSyntaxException() throw PatternSyntaxException("Illegal inline construct", patternString, curTokenIndex)
} }
positive = false positive = false
} }
@@ -620,7 +632,7 @@ internal class Lexer(val patternString: String, flags: Int) {
} }
nextIndex() nextIndex()
} }
throw PatternSyntaxException() throw PatternSyntaxException("Illegal inline construct", patternString, curTokenIndex)
} }
/** Parse character classes names and verifies correction of the syntax */ /** 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) sb.append(char)
char = pattern[nextIndex()] 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() val res = sb.toString()
return when { return when {
@@ -651,7 +663,7 @@ internal class Lexer(val patternString: String, flags: Int) {
} }
/** Process hexadecimal integer. */ /** Process hexadecimal integer. */
private fun readHex(max: Int): Int { private fun readHex(radixName: String, max: Int): Int {
val builder = StringBuilder(max) val builder = StringBuilder(max)
val length = pattern.size - 2 val length = pattern.size - 2
var i = 0 var i = 0
@@ -664,7 +676,7 @@ internal class Lexer(val patternString: String, flags: Int) {
return builder.toString().toInt(16) return builder.toString().toInt(16)
} catch (e: NumberFormatException) {} } catch (e: NumberFormatException) {}
} }
throw PatternSyntaxException() throw PatternSyntaxException("Invalid $radixName escape sequence", patternString, curTokenIndex)
} }
/** Process octal integer. */ /** Process octal integer. */
@@ -673,7 +685,7 @@ internal class Lexer(val patternString: String, flags: Int) {
var result = 0 var result = 0
var digit = digitOf(pattern[index], 8) var digit = digitOf(pattern[index], 8)
if (digit == -1) { if (digit == -1) {
throw PatternSyntaxException() throw PatternSyntaxException("Invalid octal escape sequence", patternString, curTokenIndex)
} }
val max = if (digit > 3) 2 else 3 val max = if (digit > 3) 2 else 3
var i = 0 var i = 0
@@ -63,7 +63,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
startNode = processExpression(-1, this.flags, null) startNode = processExpression(-1, this.flags, null)
if (!lexemes.isEmpty()) { if (!lexemes.isEmpty()) {
throw PatternSyntaxException() throw PatternSyntaxException("Trailing characters", pattern, lexemes.curTokenIndex)
} }
// Finalize compilation // Finalize compilation
@@ -309,7 +309,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
} }
lexemes.currentChar == Lexer.CHAR_RIGHT_PARENTHESIS -> { lexemes.currentChar == Lexer.CHAR_RIGHT_PARENTHESIS -> {
if (last is FinalSet) { if (last is FinalSet) {
throw PatternSyntaxException() throw PatternSyntaxException("unmatched )", pattern, lexemes.curTokenIndex)
} }
cur = EmptySet(last) 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. term = processExpression(char and 0xff00ffff.toInt(), newFlags, last) // Remove flags from the token.
if (lexemes.currentChar != Lexer.CHAR_RIGHT_PARENTHESIS) { if (lexemes.currentChar != Lexer.CHAR_RIGHT_PARENTHESIS) {
throw PatternSyntaxException() throw PatternSyntaxException("unmatched (", pattern, lexemes.curTokenIndex)
} }
lexemes.next() lexemes.next()
} else { } else {
@@ -488,7 +488,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
term = processRange(negative, last) term = processRange(negative, last)
if (lexemes.currentChar != Lexer.CHAR_RIGHT_SQUARE_BRACKET) { if (lexemes.currentChar != Lexer.CHAR_RIGHT_SQUARE_BRACKET) {
throw PatternSyntaxException() throw PatternSyntaxException("unmatched [", pattern, lexemes.curTokenIndex)
} }
lexemes.setModeWithReread(Lexer.Mode.PATTERN) lexemes.setModeWithReread(Lexer.Mode.PATTERN)
lexemes.next() lexemes.next()
@@ -560,7 +560,7 @@ internal class Pattern(val pattern: String, flags: Int = 0) {
backRefs[number]!!.isBackReferenced = true backRefs[number]!!.isBackReferenced = true
needsBackRefReplacement = true // And process back references in the second pass. needsBackRefReplacement = true // And process back references in the second pass.
} else { } 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 -> { char == Lexer.CHAR_RIGHT_PARENTHESIS -> {
if (last is FinalSet) { if (last is FinalSet) {
throw PatternSyntaxException() throw PatternSyntaxException("unmatched )", pattern, lexemes.curTokenIndex)
} }
term = EmptySet(last) 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) result.add(buffer, cur)
} catch (e: Exception) { } catch (e: Exception) {
throw PatternSyntaxException() throw PatternSyntaxException("Illegal character range", pattern, lexemes.curTokenIndex)
} }
lexemes.next() lexemes.next()
buffer = -1 buffer = -1
} else { } 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 notClosed = lexemes.currentChar != Lexer.CHAR_RIGHT_SQUARE_BRACKET
} }
if (notClosed) { if (notClosed) {
throw PatternSyntaxException() throw PatternSyntaxException("Missing ']'", pattern, lexemes.curTokenIndex)
} }
if (buffer >= 0) { if (buffer >= 0) {
result.add(buffer) result.add(buffer)