refactor: simplify AvailableState handling and improve matchAll logic in RegexItem
This commit is contained in:
parent
34052d1ea2
commit
b7b71fb09d
1 changed files with 59 additions and 103 deletions
|
@ -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) // 두 매칭 결과를 합쳐서 반환
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue