[regex] Clarify exception messages
This commit is contained in:
committed by
Ilya Matveev
parent
54beb0c8e8
commit
a82dd82402
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user