[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
|
* [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)
|
||||||
|
|||||||
Reference in New Issue
Block a user