Skip to content

Commit

Permalink
Fix non-thread safe regex matching in the JVM implementation
Browse files Browse the repository at this point in the history
Close #39
  • Loading branch information
h0tk3y committed Apr 20, 2021
1 parent 52064dc commit 43f2f00
Showing 1 changed file with 40 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,54 @@ package com.github.h0tk3y.betterParse.lexer
import java.util.*
import java.util.regex.Matcher

public actual class RegexToken : Token {
private val pattern: String
public actual class RegexToken private constructor(
name: String?,
ignored: Boolean,
private val pattern: String,
private val regex: Regex
private val matcher: Matcher
) : Token(name, ignored) {

private companion object {
const val inputStartPrefix = "\\A"
private val threadLocalMatcher = object : ThreadLocal<Matcher>() {
override fun initialValue() = regex.toPattern().matcher("")
}

private fun prependPatternWithInputStart(patternString: String, options: Set<RegexOption>) =
if (patternString.startsWith(inputStartPrefix))
patternString.toRegex(options)
else {
val newlineAfterComments = if (RegexOption.COMMENTS in options) "\n" else ""
val patternToEmbed = if (RegexOption.LITERAL in options) Regex.escape(patternString) else patternString
("$inputStartPrefix(?:$patternToEmbed$newlineAfterComments)").toRegex(options - RegexOption.LITERAL)
}
private val matcher: Matcher get() = threadLocalMatcher.get()

private companion object {
private const val inputStartPrefix = "\\A"

public actual constructor(name: String?, @Language("RegExp", "", "") patternString: String, ignored: Boolean)
: super(name, ignored) {
pattern = patternString
regex = prependPatternWithInputStart(patternString, emptySet())
private fun prependPatternWithInputStart(patternString: String, options: Set<RegexOption>) =
if (patternString.startsWith(Companion.inputStartPrefix))
patternString.toRegex(options)
else {
val newlineAfterComments = if (RegexOption.COMMENTS in options) "\n" else ""
val patternToEmbed = if (RegexOption.LITERAL in options) Regex.escape(patternString) else patternString
("${inputStartPrefix}(?:$patternToEmbed$newlineAfterComments)").toRegex(options - RegexOption.LITERAL)
}

matcher = regex.toPattern().matcher("")
}

public actual constructor(name: String?, regex: Regex, ignored: Boolean)
: super(name, ignored) {
pattern = regex.pattern
this.regex = prependPatternWithInputStart(pattern, regex.options)
matcher = this.regex.toPattern().matcher("")
}
public actual constructor(
name: String?,
@Language("RegExp", "", "") patternString: String,
ignored: Boolean
) : this(
name,
ignored,
patternString,
prependPatternWithInputStart(patternString, emptySet())
)

public actual constructor(
name: String?,
regex: Regex,
ignored: Boolean
) : this(
name,
ignored,
regex.pattern,
prependPatternWithInputStart(regex.pattern, regex.options)
)

override fun match(input: CharSequence, fromIndex: Int): Int {
matcher.reset(input).region(fromIndex, input.length)
Expand Down

0 comments on commit 43f2f00

Please sign in to comment.