my-regex-engine/lib/src/main/kotlin/org/example/RegexItem.kt
2025-06-28 17:50:28 +09:00

160 lines
5.5 KiB
Kotlin

package org.example
data class State(
val matched: String,
val remaining: String,
)
data class AvailableState(val seq: Sequence<State> = emptySequence()) : Sequence<State> by seq {
val isEmpty: Boolean
get() = seq.none()
}
class MatchResult(val available: AvailableState) {
val isSuccess: Boolean
get() = !this.available.isEmpty
val isFailure: Boolean
get() = this.available.isEmpty
override fun toString(): String {
return if (isSuccess) {
"MatchResult(success)"
} else {
"MatchResult(failure)"
}
}
}
// 재귀 하향 분석기.
interface RegexItem {
override fun toString(): String
fun findMatch(item: String): AvailableState
}
fun RegexItem.match(item: String): MatchResult {
// 기본 매칭 함수. AvailableState를 MatchResult로 변환
return MatchResult(this.findMatch(item))
}
class AndThenItem(val left: RegexItem, val right: RegexItem) : RegexItem {
override fun toString(): String = "${left}${right}"
override fun findMatch(item: String): AvailableState {
val leftMatch = left.findMatch(item)
if (leftMatch.isEmpty) {
return AvailableState() // If left match fails, return empty sequence
}
// If left match is successful, try to match the right item with the remaining string
// from the left match.
return AvailableState(
leftMatch.flatMap { state ->
val rightMatch = right.findMatch(state.remaining)
if (!rightMatch.isEmpty) {
// If right match is successful, combine the matched parts
rightMatch.map { rightState ->
State(state.matched + rightState.matched, rightState.remaining)
}
} else {
// If right match fails, return an empty sequence
sequenceOf()
}
}
)
}
}
class CharItem(val value: String) : RegexItem {
override fun toString(): String = value
override fun findMatch(item: String): AvailableState {
return if (item.isNotEmpty() && item[0].toString() == value) {
// If the first character matches, return a successful match
AvailableState(sequenceOf(State(value, item.substring(1))))
} else {
// If it doesn't match, return an empty sequence
AvailableState()
}
}
}
class PlusItem(val item: RegexItem) : RegexItem {
override fun toString(): String = "${item}+"
override fun findMatch(item: String): AvailableState {
// 욕심쟁이 매칭해야 함. 그래서 가능한 한 많이 매칭해야 함.
val results = mutableListOf<State>()
var remaining = item
while (true) {
val matchResult = this.item.findMatch(remaining)
if (matchResult.isEmpty) {
break // No more matches possible
}
matchResult.forEach { state -> results.add(State(state.matched, state.remaining)) }
remaining = matchResult.first().remaining // Update remaining string
}
return AvailableState(results.reversed().asSequence()) // Return all successful matches
}
}
class StarItem(val item: RegexItem) : RegexItem {
override fun toString(): String = "${item}*"
override fun findMatch(item: String): AvailableState {
// 욕심쟁이 매칭해야 함. 그래서 가능한 한 많이 매칭해야 함.
val results = mutableListOf<State>(State("", item)) // Start with an empty match
var remaining = item
while (true) {
val matchResult = this.item.findMatch(remaining)
if (matchResult.isEmpty) {
break // No more matches possible
}
matchResult.forEach { state -> results.add(State(state.matched, state.remaining)) }
remaining = matchResult.first().remaining // Update remaining string
if (remaining.isEmpty()) {
break // If remaining string is empty, stop matching
}
}
// 반드시 reverse해서 가장 긴 매칭부터 시작해야 함.
return AvailableState(results.reversed().asSequence()) // Return all successful matches
}
}
class QuestionItem(val item: RegexItem) : RegexItem {
override fun toString(): String = "${item}?"
override fun findMatch(item: String): AvailableState {
// ?는 0개 또는 1개 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환
val matchResult = this.item.findMatch(item)
if (matchResult.isEmpty) {
// If the item does not match, return an empty sequence
return AvailableState(sequenceOf(State("", item)))
}
// If it matches, return the successful match
return AvailableState(matchResult.map { State(it.matched, it.remaining) })
}
}
class DotItem : RegexItem {
override fun toString(): String = "."
override fun findMatch(item: String): AvailableState {
// .은 임의의 한 문자와 매칭되므로, 첫 문자가 존재하면 매칭 성공
return if (item.isNotEmpty()) {
AvailableState(sequenceOf(State(item[0].toString(), item.substring(1))))
} else {
AvailableState() // 빈 문자열에 대해서는 매칭 실패
}
}
}
class AlternationItem(val left: RegexItem, val right: RegexItem) : RegexItem {
override fun toString(): String = "(${left}|${right})"
override fun findMatch(item: String): AvailableState {
// Alternation은 왼쪽 또는 오른쪽 항목 중 하나와 매칭되므로, 각각 시도해보고 성공하는 경우를 반환
val leftMatch = left.findMatch(item)
val rightMatch = right.findMatch(item)
return AvailableState(
(leftMatch + rightMatch).asSequence() // 두 매칭 결과를 합쳐서 반환
)
}
}