160 lines
5.5 KiB
Kotlin
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() // 두 매칭 결과를 합쳐서 반환
|
|
)
|
|
}
|
|
}
|