refactor: simplify AvailableState handling and improve matchAll logic in RegexItem

This commit is contained in:
monoid 2025-06-29 15:54:00 +09:00
parent 34052d1ea2
commit b7b71fb09d

View file

@ -12,13 +12,10 @@ data class State(
get() = input.substring(endIndex) get() = input.substring(endIndex)
} }
data class AvailableState(val seq: Sequence<State> = emptySequence()) : Sequence<State> by seq { typealias AvailableState = Sequence<State>
val isEmpty: Boolean
get() = seq.none()
}
// 재귀 하향 분석기. // 재귀 하향 분석기.
// 백트랙킹 기반.
interface RegexItem { interface RegexItem {
override fun toString(): String override fun toString(): String
fun findMatch(str: String, position: Int = 0): AvailableState fun findMatch(str: String, position: Int = 0): AvailableState
@ -26,27 +23,26 @@ interface RegexItem {
fun RegexItem.matchAll(item: String): Sequence<State> { fun RegexItem.matchAll(item: String): Sequence<State> {
return sequence { return sequence {
var position = 0; var position = 0
// 문자열의 끝까지 반복합니다. 비어있어도 한번은 시도합니다. // 문자열의 끝까지 반복합니다. 비어있어도 한번은 시도합니다.
while (position <= item.length) { while (position <= item.length) {
// findMatch 메서드를 호출하여 매칭을 시도합니다. // findMatch 메서드를 호출하여 매칭을 시도합니다.
val matchResult = findMatch(item, position) val matchResult = findMatch(item, position).firstOrNull()
if (matchResult.isEmpty) { if (matchResult === null) {
// 매칭이 실패하면 position을 증가시키고 다시 시도합니다. // 매칭이 실패하면 position을 증가시키고 다시 시도합니다.
if (position < item.length) { position++
position++ continue
continue
} else {
// position이 문자열의 끝에 도달했지만 매칭이 실패한 경우 종료
break
}
} else {
// 매칭이 성공하면 MatchResult를 생성하여 반환합니다.
val state = matchResult.seq.first()
yield(state)
// 다음 위치로 이동합니다.
position = state.endIndex
} }
// 매칭이 성공하면 MatchResult를 생성하여 반환합니다.
yield(matchResult)
// 다음 위치로 이동합니다.
position =
if (matchResult.startIndex == matchResult.endIndex) {
position + 1
} else {
matchResult.endIndex
}
} }
} }
} }
@ -65,31 +61,17 @@ class AndThenItem(val left: RegexItem, val right: RegexItem) : RegexItem {
override fun toString(): String = "${left}${right}" override fun toString(): String = "${left}${right}"
override fun findMatch(str: String, position: Int): AvailableState { override fun findMatch(str: String, position: Int): AvailableState {
val leftMatch = left.findMatch(str, position) val leftMatch = left.findMatch(str, position)
if (leftMatch.isEmpty) { return leftMatch.flatMap { leftState ->
return AvailableState() // If left match fails, return empty sequence right.findMatch(str, leftState.endIndex).map { rightState ->
// If right match is successful, combine the matched parts
State(
str,
leftState.startIndex,
rightState.endIndex,
leftState.captures + rightState.captures // Combine captures
)
}
} }
// If left match is successful, try to match the right item with the remaining string
// from the left match.
return AvailableState(
leftMatch.flatMap { leftState ->
val rightMatch = right.findMatch(str, leftState.endIndex)
if (!rightMatch.isEmpty) {
// If right match is successful, combine the matched parts
rightMatch.map { rightState ->
State(
str,
leftState.startIndex,
rightState.endIndex,
leftState.captures + rightState.captures
) // Combine captures
}
} else {
// If right match fails, return an empty sequence
emptySequence()
}
}
)
} }
} }
@ -113,9 +95,9 @@ class CharItem(val value: String) : RegexItem {
return when { return when {
// 첫번째 문자가 value와 일치하는지 확인 // 첫번째 문자가 value와 일치하는지 확인
position < str.length && str[position].toString() == value -> { position < str.length && str[position].toString() == value -> {
AvailableState(sequenceOf(State(str, position, position + 1))) (sequenceOf(State(str, position, position + 1)))
} }
else -> AvailableState() else -> emptySequence() // 일치하지 않으면 빈 시퀀스 반환
} }
} }
} }
@ -128,9 +110,9 @@ class BracketItem(val content: String) : RegexItem {
// 대괄호 안의 내용과 일치하는 첫 문자를 찾음 // 대괄호 안의 내용과 일치하는 첫 문자를 찾음
return when { return when {
position < str.length && content.contains(str[position]) -> { position < str.length && content.contains(str[position]) -> {
AvailableState(sequenceOf(State(str, position, position + 1))) (sequenceOf(State(str, position, position + 1)))
} }
else -> AvailableState() else -> emptySequence() // 일치하지 않으면 빈 시퀀스 반환
} }
} }
} }
@ -142,11 +124,7 @@ class GroupItem(val item: RegexItem, val name: String) : RegexItem {
// 그룹은 내부 아이템과 동일하게 매칭을 시도 // 그룹은 내부 아이템과 동일하게 매칭을 시도
val ret = item.findMatch(str, position) val ret = item.findMatch(str, position)
// 매칭된 상태에 그룹 이름을 추가하여 반환 // 매칭된 상태에 그룹 이름을 추가하여 반환
return AvailableState( return ret.map { state -> state.copy(captures = state.captures + (name to state.matched)) }
ret.seq.map { state ->
state.copy(captures = state.captures + (name to state.matched))
}
)
} }
} }
@ -156,29 +134,27 @@ class AnchorItem(val anchor: String) : RegexItem {
override fun findMatch(str: String, position: Int): AvailableState { override fun findMatch(str: String, position: Int): AvailableState {
// 앵커는 문자열의 시작(^) 또는 끝($)과 매칭됨 // 앵커는 문자열의 시작(^) 또는 끝($)과 매칭됨
return when (anchor) { return when (anchor) {
"^" -> if (position == 0) { "^" ->
AvailableState(sequenceOf(State(str, 0, 0))) if (position == 0) {
} else { (sequenceOf(State(str, 0, 0)))
AvailableState() // 시작 앵커가 실패하면 빈 시퀀스 반환 } else {
} emptySequence() // 시작 앵커가 실패하면 빈 시퀀스 반환
"$" -> if (position == str.length) { }
AvailableState(sequenceOf(State(str, str.length, str.length))) "$" ->
} else { if (position == str.length) {
AvailableState() // 끝 앵커가 실패하면 빈 시퀀스 반환 (sequenceOf(State(str, str.length, str.length)))
} } else {
emptySequence() // 끝 앵커가 실패하면 빈 시퀀스 반환
}
// 다른 앵커는 지원하지 않음 // 다른 앵커는 지원하지 않음
else -> throw IllegalArgumentException("Unknown anchor: $anchor") else -> throw IllegalArgumentException("Unknown anchor: $anchor")
} }
} }
} }
fun matchMany( fun matchMany(str: String, item: RegexItem, position: Int): Sequence<State> {
str: String,
item: RegexItem,
position: Int
): Sequence<State> {
// 욕심쟁이 매칭을 위한 헬퍼 함수 // 욕심쟁이 매칭을 위한 헬퍼 함수
return item.findMatch(str, position).seq.flatMap { state -> return item.findMatch(str, position).flatMap { state ->
if (state.endIndex == str.length) { if (state.endIndex == str.length) {
sequenceOf(state) // If remaining is empty, return the matched state sequenceOf(state) // If remaining is empty, return the matched state
} else { } else {
@ -190,26 +166,10 @@ fun matchMany(
} }
} }
// fun matchMany(str: String, item: RegexItem): Sequence<State> = sequence {
// val stack = mutableListOf(item.findMatch(str).seq)
// while (stack.isNotEmpty()) {
// val current = stack.removeAt(stack.lastIndex)
// for (state in current) {
// yield(state) // Yield the current state
// if (state.remaining.isNotEmpty()) {
// // If there is remaining string, continue matching
// stack.add(item.findMatch(state.remaining).seq.map { nextState ->
// State(state.matched + nextState.matched, nextState.remaining)
// })
// }
// }
// }
// }
class PlusItem(val item: RegexItem) : RegexItem { class PlusItem(val item: RegexItem) : RegexItem {
override fun toString(): String = "${item}+" override fun toString(): String = "${item}+"
override fun findMatch(str: String, position: Int): AvailableState { override fun findMatch(str: String, position: Int): AvailableState {
return AvailableState(matchMany(str, item, position)) return (matchMany(str, item, position))
} }
} }
@ -218,16 +178,14 @@ class StarItem(val item: RegexItem) : RegexItem {
override fun findMatch(str: String, position: Int): AvailableState { override fun findMatch(str: String, position: Int): AvailableState {
// *는 0개 이상의 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환 // *는 0개 이상의 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환
val matchResult = this.item.findMatch(str, position) val matchResult = this.item.findMatch(str, position)
if (matchResult.isEmpty) { if (matchResult.none()) {
// If the item does not match, return an empty sequence // 아이템이 매칭되지 않으면 빈 시퀀스를 반환
return AvailableState(sequenceOf(State(str, position, position))) return (sequenceOf(State(str, position, position)))
} }
// If it matches, return the successful match and continue matching with the remaining string // If it matches, return the successful match and continue matching with the remaining string
return AvailableState( return (matchResult.flatMap { state ->
matchResult.flatMap { state -> matchMany(str, this.item, state.endIndex) + sequenceOf(state)
sequenceOf(state) + matchMany(str, this.item, state.endIndex) })
}
)
} }
} }
@ -236,12 +194,12 @@ class QuestionItem(val item: RegexItem) : RegexItem {
override fun findMatch(str: String, position: Int): AvailableState { override fun findMatch(str: String, position: Int): AvailableState {
// ?는 0개 또는 1개 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환 // ?는 0개 또는 1개 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환
val matchResult = this.item.findMatch(str, position) val matchResult = this.item.findMatch(str, position)
if (matchResult.isEmpty) { if (matchResult.none()) {
// If the item does not match, return an empty sequence // If the item does not match, return an empty sequence
return AvailableState(sequenceOf(State(str, position, position))) return (sequenceOf(State(str, position, position)))
} }
// If it matches, return the successful match // If it matches, return the successful match
return AvailableState(matchResult.map { State(str, it.startIndex, it.endIndex) }) return (matchResult.map { State(str, it.startIndex, it.endIndex) })
} }
} }
@ -250,9 +208,8 @@ class DotItem : RegexItem {
override fun findMatch(str: String, position: Int): AvailableState = override fun findMatch(str: String, position: Int): AvailableState =
// .은 임의의 한 문자와 매칭되므로, 첫 문자가 존재하면 매칭 성공 // .은 임의의 한 문자와 매칭되므로, 첫 문자가 존재하면 매칭 성공
when { when {
position < str.length -> position < str.length -> (sequenceOf(State(str, position, position + 1)))
AvailableState(sequenceOf(State(str, position, position + 1))) else -> emptySequence() // 빈 문자열에 대해서는 매칭 실패
else -> AvailableState() // 빈 문자열에 대해서는 매칭 실패
} }
} }
@ -263,8 +220,7 @@ class AlternationItem(val left: RegexItem, val right: RegexItem) : RegexItem {
val leftMatch = left.findMatch(str, position) val leftMatch = left.findMatch(str, position)
val rightMatch = right.findMatch(str, position) val rightMatch = right.findMatch(str, position)
return AvailableState( return ((leftMatch + rightMatch) // 두 매칭 결과를 합쳐서 반환
(leftMatch + rightMatch) // 두 매칭 결과를 합쳐서 반환
) )
} }
} }