Compare commits
No commits in common. "c7926c1f4daa5c40b7999e8429945f5daf392c34" and "7ae97961b21ad98e278f600f78ae58cc2730337c" have entirely different histories.
c7926c1f4d
...
7ae97961b2
3 changed files with 198 additions and 329 deletions
|
@ -2,45 +2,16 @@ package org.example
|
||||||
|
|
||||||
import com.github.h0tk3y.betterParse.combinators.*
|
import com.github.h0tk3y.betterParse.combinators.*
|
||||||
import com.github.h0tk3y.betterParse.grammar.Grammar
|
import com.github.h0tk3y.betterParse.grammar.Grammar
|
||||||
import com.github.h0tk3y.betterParse.grammar.parseToEnd
|
|
||||||
import com.github.h0tk3y.betterParse.grammar.parser
|
import com.github.h0tk3y.betterParse.grammar.parser
|
||||||
import com.github.h0tk3y.betterParse.lexer.literalToken
|
import com.github.h0tk3y.betterParse.lexer.literalToken
|
||||||
import com.github.h0tk3y.betterParse.lexer.regexToken
|
import com.github.h0tk3y.betterParse.lexer.regexToken
|
||||||
import com.github.h0tk3y.betterParse.lexer.token
|
|
||||||
import com.github.h0tk3y.betterParse.parser.Parser
|
import com.github.h0tk3y.betterParse.parser.Parser
|
||||||
|
|
||||||
class RegexParser : Grammar<RegexItem>() {
|
class RegexParser : Grammar<RegexItem>() {
|
||||||
private var groupCounter = 0
|
|
||||||
|
|
||||||
val escapedCharacter by regexToken("\\\\[+*?.$^()|\\[\\]]")
|
|
||||||
val postfixOperator by regexToken("[+*?]")
|
val postfixOperator by regexToken("[+*?]")
|
||||||
val anchorOperator by regexToken("[$^]")
|
|
||||||
val alternationSymbol by literalToken("|")
|
val alternationSymbol by literalToken("|")
|
||||||
val openParenSymbol by literalToken("(")
|
val openParenSymbol by literalToken("(")
|
||||||
val closeParenSymbol by literalToken(")")
|
val closeParenSymbol by literalToken(")")
|
||||||
val bracketContent by
|
|
||||||
token(
|
|
||||||
name = "bracketContent",
|
|
||||||
matcher = { seq, from ->
|
|
||||||
if (seq[from] != '[') {
|
|
||||||
0 // 대괄호로 시작하지 않으면 매칭 실패
|
|
||||||
} else {
|
|
||||||
// 대괄호의 시작 위치에서부터 ']'를 찾음
|
|
||||||
var to = seq.indexOf(']', from)
|
|
||||||
// 이스케이프 ']' 건너 뛰기
|
|
||||||
while (to >= 0 && to > from && seq[to - 1] == '\\') {
|
|
||||||
to = seq.indexOf(']', to + 1)
|
|
||||||
}
|
|
||||||
if (to < 0) {
|
|
||||||
0
|
|
||||||
} else if (to == from + 1) {
|
|
||||||
0 // 빈 대괄호는 허용하지 않음
|
|
||||||
} else {
|
|
||||||
to - from + 1 // 대괄호의 시작 위치부터 ']'까지의 길이
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val dot by literalToken(".")
|
val dot by literalToken(".")
|
||||||
val charToken by regexToken("[a-zA-Z0-9]")
|
val charToken by regexToken("[a-zA-Z0-9]")
|
||||||
|
@ -49,27 +20,24 @@ class RegexParser : Grammar<RegexItem>() {
|
||||||
|
|
||||||
val item: Parser<RegexItem> by
|
val item: Parser<RegexItem> by
|
||||||
char or
|
char or
|
||||||
(anchorOperator map { AnchorItem(it.text) }) or
|
|
||||||
(dot asJust DotItem()) or
|
(dot asJust DotItem()) or
|
||||||
(escapedCharacter map { CharItem(it.text.substring(1)) }) or
|
(skip(openParenSymbol) and (parser(::rootParser)) and skip(closeParenSymbol))
|
||||||
(bracketContent map { BracketItem(it.text.substring(1, it.text.length - 1)) }) or
|
|
||||||
(skip(openParenSymbol) and
|
|
||||||
(parser(::rootParser)) and
|
|
||||||
skip(closeParenSymbol) map
|
|
||||||
{
|
|
||||||
val groupName = "${groupCounter++}"
|
|
||||||
GroupItem(it, groupName)
|
|
||||||
})
|
|
||||||
|
|
||||||
val term: Parser<RegexItem> by
|
val term: Parser<RegexItem> by
|
||||||
(item and optional(postfixOperator)) map { (item, op) ->
|
(item and optional(postfixOperator)) map
|
||||||
when (op?.text) {
|
{ result ->
|
||||||
"+" -> PlusItem(item)
|
result.t1.let { first ->
|
||||||
"*" -> StarItem(item)
|
result.t2?.let {
|
||||||
"?" -> QuestionItem(item)
|
when (it.text) {
|
||||||
else -> item
|
"+" -> PlusItem(first)
|
||||||
}
|
"*" -> StarItem(first)
|
||||||
}
|
"?" -> QuestionItem(first)
|
||||||
|
else -> first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?: first
|
||||||
|
}
|
||||||
|
}
|
||||||
val andThen: Parser<RegexItem> by
|
val andThen: Parser<RegexItem> by
|
||||||
oneOrMore(term) map { items -> items.reduce { left, right -> AndThenItem(left, right) } }
|
oneOrMore(term) map { items -> items.reduce { left, right -> AndThenItem(left, right) } }
|
||||||
val termWithAlternation: Parser<RegexItem> by
|
val termWithAlternation: Parser<RegexItem> by
|
||||||
|
@ -80,7 +48,3 @@ class RegexParser : Grammar<RegexItem>() {
|
||||||
|
|
||||||
override val rootParser: Parser<RegexItem> by termWithAlternation
|
override val rootParser: Parser<RegexItem> by termWithAlternation
|
||||||
}
|
}
|
||||||
|
|
||||||
fun compileRegex(input: String): RegexItem {
|
|
||||||
return RegexParser().parseToEnd(input)
|
|
||||||
}
|
|
|
@ -1,208 +1,175 @@
|
||||||
package org.example
|
package org.example
|
||||||
|
|
||||||
data class State(
|
data class State(
|
||||||
val input: String,
|
val matched: String,
|
||||||
val startIndex: Int,
|
val remaining: String,
|
||||||
val endIndex: Int,
|
)
|
||||||
val captures: Map<String, String> = emptyMap()
|
|
||||||
) {
|
data class AvailableState(val seq: Sequence<State> = emptySequence()) : Sequence<State> by seq {
|
||||||
val matched: String
|
val isEmpty: Boolean
|
||||||
get() = input.substring(startIndex, endIndex)
|
get() = seq.none()
|
||||||
val remaining: String
|
|
||||||
get() = input.substring(endIndex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias AvailableState = Sequence<State>
|
class MatchResult(val available: AvailableState) {
|
||||||
|
|
||||||
// 재귀 하향 분석기.
|
val isSuccess: Boolean
|
||||||
// 백트랙킹 기반.
|
get() = !this.available.isEmpty
|
||||||
interface RegexItem {
|
|
||||||
override fun toString(): String
|
|
||||||
fun findMatch(str: String, position: Int = 0): AvailableState
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RegexItem.findAll(item: String): Sequence<State> {
|
val isFailure: Boolean
|
||||||
return sequence {
|
get() = this.available.isEmpty
|
||||||
var position = 0
|
|
||||||
// 문자열의 끝까지 반복합니다. 비어있어도 한번은 시도합니다.
|
|
||||||
while (position <= item.length) {
|
|
||||||
// findMatch 메서드를 호출하여 매칭을 시도합니다.
|
|
||||||
val matchResult = findMatch(item, position).firstOrNull()
|
|
||||||
if (matchResult == null) {
|
|
||||||
// 매칭이 실패하면 position을 증가시키고 다시 시도합니다.
|
|
||||||
position++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 매칭이 성공하면 MatchResult를 생성하여 반환합니다.
|
|
||||||
yield(matchResult)
|
|
||||||
|
|
||||||
// 다음 위치로 이동합니다.
|
override fun toString(): String {
|
||||||
position =
|
return if (isSuccess) {
|
||||||
if (matchResult.startIndex == matchResult.endIndex) {
|
"MatchResult(success)"
|
||||||
position + 1
|
} else {
|
||||||
} else {
|
"MatchResult(failure)"
|
||||||
matchResult.endIndex
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RegexItem.find(item: String): State? {
|
// 재귀 하향 분석기.
|
||||||
// findAll 에서 첫 번째 매칭 결과를 반환합니다.
|
interface RegexItem {
|
||||||
return this.findAll(item).firstOrNull()
|
override fun toString(): String
|
||||||
|
fun findMatch(str: String): AvailableState
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RegexItem.containsMatchIn(item: String): Boolean {
|
fun RegexItem.match(item: String): MatchResult {
|
||||||
// 매칭 결과가 성공인지 확인하는 헬퍼 함수
|
// 기본 매칭 함수. AvailableState를 MatchResult로 변환
|
||||||
return this.find(item) != null
|
return MatchResult(this.findMatch(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
class AndThenItem(val left: RegexItem, val right: RegexItem) : RegexItem {
|
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): AvailableState {
|
||||||
val leftMatch = left.findMatch(str, position)
|
val leftMatch = left.findMatch(str)
|
||||||
return leftMatch.flatMap { leftState ->
|
if (leftMatch.isEmpty) {
|
||||||
right.findMatch(str, leftState.endIndex).map { rightState ->
|
return AvailableState() // If left match fails, return empty sequence
|
||||||
// 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 { 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
|
||||||
|
emptySequence()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CharItem(val value: String) : RegexItem {
|
class CharItem(val value: String) : RegexItem {
|
||||||
companion object {
|
override fun toString(): String = value
|
||||||
private val META_CHARS = setOf("\\", "+", "*", "?", ".", "(", ")", "|", "[", "]", "^", "$")
|
override fun findMatch(str: String): AvailableState {
|
||||||
}
|
|
||||||
override fun toString(): String =
|
|
||||||
if (value in META_CHARS) "\\$value" else value
|
|
||||||
|
|
||||||
override fun findMatch(str: String, position: Int): AvailableState {
|
|
||||||
return when {
|
return when {
|
||||||
// 첫번째 문자가 value와 일치하는지 확인
|
// 첫번째 문자가 value와 일치하는지 확인
|
||||||
position < str.length && str[position].toString() == value -> {
|
str.isNotEmpty() && str[0].toString() == value -> {
|
||||||
sequenceOf(State(str, position, position + 1))
|
AvailableState(sequenceOf(State(value, str.substring(1))))
|
||||||
}
|
}
|
||||||
else -> emptySequence() // 일치하지 않으면 빈 시퀀스 반환
|
else -> AvailableState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BracketItem(val content: String) : RegexItem {
|
fun matchMany(
|
||||||
override fun toString(): String = "[$content]"
|
str: String,
|
||||||
|
item: RegexItem,
|
||||||
// TODO: 범위 처리
|
): Sequence<State> {
|
||||||
override fun findMatch(str: String, position: Int): AvailableState {
|
|
||||||
// 대괄호 안의 내용과 일치하는 첫 문자를 찾음
|
|
||||||
return when {
|
|
||||||
position < str.length && content.contains(str[position]) -> {
|
|
||||||
sequenceOf(State(str, position, position + 1))
|
|
||||||
}
|
|
||||||
else -> emptySequence() // 일치하지 않으면 빈 시퀀스 반환
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GroupItem(val item: RegexItem, val name: String) : RegexItem {
|
|
||||||
override fun toString(): String = "(${item})"
|
|
||||||
|
|
||||||
override fun findMatch(str: String, position: Int): AvailableState {
|
|
||||||
// 그룹은 내부 아이템과 동일하게 매칭을 시도
|
|
||||||
val ret = item.findMatch(str, position)
|
|
||||||
// 매칭된 상태에 그룹 이름을 추가하여 반환
|
|
||||||
return ret.map { state -> state.copy(captures = state.captures + (name to state.matched)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AnchorItem(val anchor: String) : RegexItem {
|
|
||||||
override fun toString(): String = anchor
|
|
||||||
|
|
||||||
override fun findMatch(str: String, position: Int): AvailableState {
|
|
||||||
// 앵커는 문자열의 시작(^) 또는 끝($)과 매칭됨
|
|
||||||
return when (anchor) {
|
|
||||||
"^" ->
|
|
||||||
if (position == 0) {
|
|
||||||
sequenceOf(State(str, 0, 0))
|
|
||||||
} else {
|
|
||||||
emptySequence() // 시작 앵커가 실패하면 빈 시퀀스 반환
|
|
||||||
}
|
|
||||||
"$" ->
|
|
||||||
if (position == str.length) {
|
|
||||||
sequenceOf(State(str, str.length, str.length))
|
|
||||||
} else {
|
|
||||||
emptySequence() // 끝 앵커가 실패하면 빈 시퀀스 반환
|
|
||||||
}
|
|
||||||
// 다른 앵커는 지원하지 않음
|
|
||||||
else -> throw IllegalArgumentException("Unknown anchor: $anchor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun matchMany(str: String, item: RegexItem, position: Int): Sequence<State> {
|
|
||||||
// 욕심쟁이 매칭을 위한 헬퍼 함수
|
// 욕심쟁이 매칭을 위한 헬퍼 함수
|
||||||
return item.findMatch(str, position).flatMap { state ->
|
return item.findMatch(str).seq.flatMap { state ->
|
||||||
if (state.endIndex == str.length) {
|
if (state.remaining.isEmpty()) {
|
||||||
sequenceOf(state) // If remaining is empty, return the matched state
|
sequenceOf(state) // If remaining is empty, return the matched state
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, continue matching with the remaining string
|
// Otherwise, continue matching with the remaining string
|
||||||
matchMany(str, item, state.endIndex).map { nextState ->
|
matchMany(state.remaining, item).map { nextState ->
|
||||||
State(str, state.startIndex, nextState.endIndex)
|
State(state.matched + nextState.matched, nextState.remaining)
|
||||||
} + sequenceOf(state) // Include the current state as well
|
} + sequenceOf(state) // Include the current state as well
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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): AvailableState {
|
||||||
return matchMany(str, item, position)
|
return AvailableState(matchMany(str, item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StarItem(val item: RegexItem) : RegexItem {
|
class StarItem(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): AvailableState {
|
||||||
// *는 0번 또는 1번 이상 일치합니다.
|
// *는 0개 이상의 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환
|
||||||
// 욕심쟁이(greedy) 방식으로 구현하기 위해, 가장 긴 매치(1번 이상)를 먼저 찾고, 그 다음에 0번 매치를 추가합니다.
|
val matchResult = this.item.findMatch(str)
|
||||||
val oneOrMoreMatches = matchMany(str, item, position)
|
if (matchResult.isEmpty) {
|
||||||
val zeroMatch = sequenceOf(State(str, position, position))
|
// If the item does not match, return an empty sequence
|
||||||
return oneOrMoreMatches + zeroMatch
|
return AvailableState(sequenceOf(State("", str)))
|
||||||
|
}
|
||||||
|
// If it matches, return the successful match and continue matching with the remaining string
|
||||||
|
return AvailableState(
|
||||||
|
matchResult.flatMap { state ->
|
||||||
|
sequenceOf(state) + matchMany(state.remaining, this.item)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class QuestionItem(val item: RegexItem) : RegexItem {
|
class QuestionItem(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): AvailableState {
|
||||||
// ?는 0번 또는 1번 일치합니다.
|
// ?는 0개 또는 1개 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환
|
||||||
val oneMatch = item.findMatch(str, position)
|
val matchResult = this.item.findMatch(str)
|
||||||
val zeroMatch = sequenceOf(State(str, position, position))
|
if (matchResult.isEmpty) {
|
||||||
// 1번 매치를 0번 매치보다 우선합니다.
|
// If the item does not match, return an empty sequence
|
||||||
return oneMatch + zeroMatch
|
return AvailableState(sequenceOf(State("", str)))
|
||||||
|
}
|
||||||
|
// If it matches, return the successful match
|
||||||
|
return AvailableState(matchResult.map { State(it.matched, it.remaining) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DotItem : RegexItem {
|
class DotItem : RegexItem {
|
||||||
override fun toString(): String = "."
|
override fun toString(): String = "."
|
||||||
override fun findMatch(str: String, position: Int): AvailableState =
|
override fun findMatch(str: String): AvailableState =
|
||||||
// .은 임의의 한 문자와 매칭되므로, 첫 문자가 존재하면 매칭 성공
|
// .은 임의의 한 문자와 매칭되므로, 첫 문자가 존재하면 매칭 성공
|
||||||
when {
|
when {
|
||||||
position < str.length -> sequenceOf(State(str, position, position + 1))
|
str.isNotEmpty() ->
|
||||||
else -> emptySequence() // 빈 문자열에 대해서는 매칭 실패
|
AvailableState(sequenceOf(State(str[0].toString(), str.substring(1))))
|
||||||
}
|
else -> AvailableState() // 빈 문자열에 대해서는 매칭 실패
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlternationItem(val left: RegexItem, val right: RegexItem) : RegexItem {
|
class AlternationItem(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): AvailableState {
|
||||||
// Alternation은 왼쪽 또는 오른쪽 항목 중 하나와 매칭되므로, 각각 시도해보고 성공하는 경우를 반환
|
// Alternation은 왼쪽 또는 오른쪽 항목 중 하나와 매칭되므로, 각각 시도해보고 성공하는 경우를 반환
|
||||||
val leftMatch = left.findMatch(str, position)
|
val leftMatch = left.findMatch(str)
|
||||||
val rightMatch = right.findMatch(str, position)
|
val rightMatch = right.findMatch(str)
|
||||||
|
|
||||||
return leftMatch + rightMatch // 두 매칭 결과를 합쳐서 반환
|
return AvailableState(
|
||||||
|
(leftMatch + rightMatch) // 두 매칭 결과를 합쳐서 반환
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,171 +2,109 @@ package org.example
|
||||||
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import com.github.h0tk3y.betterParse.grammar.parseToEnd
|
||||||
|
|
||||||
private fun checkRegex(
|
|
||||||
pattern: String,
|
|
||||||
block: RegexTestAsserter .() -> Unit
|
|
||||||
) {
|
|
||||||
val regex = compileRegex(pattern)
|
|
||||||
// 나중에는 안 같겠지만, 일단은 같다고 가정
|
|
||||||
assertEquals(pattern, regex.toString())
|
|
||||||
block(RegexTestAsserter(regex))
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RegexTestAsserter(private val regex: RegexItem) {
|
|
||||||
fun String.shouldMatch() {
|
|
||||||
assert(regex.containsMatchIn(this)) { "Expected '$this' to match" }
|
|
||||||
}
|
|
||||||
fun String.shouldNotMatch() {
|
|
||||||
assert(!regex.containsMatchIn(this)) { "Expected '$this' not to match" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ParserTest {
|
class ParserTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSimpleCharacter() {
|
fun testSimpleCharacter() {
|
||||||
checkRegex("a") {
|
val input = "a"
|
||||||
"a".shouldMatch()
|
val result = RegexParser().parseToEnd(input)
|
||||||
"b".shouldNotMatch()
|
assertEquals("a",result.toString())
|
||||||
"".shouldNotMatch()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun testCharacterWithPlus() {
|
fun testCharacterWithPlus() {
|
||||||
checkRegex("a+") {
|
val input = "a+"
|
||||||
"a".shouldMatch()
|
val parser = RegexParser()
|
||||||
"aa".shouldMatch()
|
val result = parser.parseToEnd(input)
|
||||||
"b".shouldNotMatch()
|
assertEquals("a+", result.toString())
|
||||||
"".shouldNotMatch()
|
assert(result.match("a").isSuccess)
|
||||||
}
|
assert(result.match("aa").isSuccess)
|
||||||
|
assert(!result.match("b").isSuccess)
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun testCharacterWithStar() {
|
fun testCharacterWithStar() {
|
||||||
checkRegex("b*") {
|
val input = "b*"
|
||||||
"b".shouldMatch()
|
val parser = RegexParser()
|
||||||
"bb".shouldMatch()
|
val result = parser.parseToEnd(input)
|
||||||
"".shouldMatch() // 빈 문자열도 매칭됨
|
assertEquals("b*", result.toString())
|
||||||
}
|
assert(result.match("").isSuccess)
|
||||||
|
assert(result.match("b").isSuccess)
|
||||||
|
assert(result.match("bb").isSuccess)
|
||||||
|
assert(result.match("a").isSuccess)
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun testCharacterWithQuestion() {
|
fun testCharacterWithQuestion() {
|
||||||
checkRegex("c?") {
|
val input = "c?"
|
||||||
"c".shouldMatch()
|
val parser = RegexParser()
|
||||||
"".shouldMatch() // 빈 문자열도 매칭됨
|
val result = parser.parseToEnd(input)
|
||||||
}
|
assertEquals("c?", result.toString())
|
||||||
|
assert(result.match("").isSuccess)
|
||||||
|
assert(result.match("c").isSuccess)
|
||||||
|
assert(result.match("cc").isSuccess)
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun testDot() {
|
fun testDot() {
|
||||||
checkRegex(".") {
|
val input = "."
|
||||||
"a".shouldMatch()
|
val parser = RegexParser()
|
||||||
"1".shouldMatch()
|
val result = parser.parseToEnd(input)
|
||||||
"".shouldNotMatch() // 빈 문자열은 매칭되지 않음
|
assertEquals(".", result.toString())
|
||||||
}
|
assert(result.match("a").isSuccess)
|
||||||
|
assert(result.match("1").isSuccess)
|
||||||
|
assert(!result.match("").isSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAlternation() {
|
fun testAlternation() {
|
||||||
checkRegex("a|b") {
|
val input = "a|b"
|
||||||
"a".shouldMatch()
|
val parser = RegexParser()
|
||||||
"b".shouldMatch()
|
val result = parser.parseToEnd(input)
|
||||||
"c".shouldNotMatch()
|
assertEquals("(a|b)", result.toString())
|
||||||
"".shouldNotMatch() // 빈 문자열은 매칭되지 않음
|
assert(result.match("a").isSuccess)
|
||||||
}
|
assert(result.match("b").isSuccess)
|
||||||
|
assert(!result.match("c").isSuccess)
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun testParentheses() {
|
fun testParentheses() {
|
||||||
checkRegex("(d)") {
|
val input = "(d)"
|
||||||
"d".shouldMatch()
|
val parser = RegexParser()
|
||||||
"e".shouldNotMatch()
|
val result = parser.parseToEnd(input)
|
||||||
"".shouldNotMatch() // 빈 문자열은 매칭되지 않음
|
assertEquals("d", result.toString())
|
||||||
}
|
assert(result.match("d").isSuccess)
|
||||||
|
assert(!result.match("e").isSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testComplexExpression() {
|
fun testComplexExpression() {
|
||||||
checkRegex("a(b|c)*d+") {
|
val input = "a(b|c)*d+"
|
||||||
"ad".shouldMatch()
|
val parser = RegexParser()
|
||||||
"ab".shouldNotMatch()
|
val result = parser.parseToEnd(input)
|
||||||
"acd".shouldMatch()
|
assertEquals("a(b|c)*d+", result.toString())
|
||||||
"abbbd".shouldMatch()
|
assert(result.match("ad").isSuccess)
|
||||||
"a".shouldNotMatch()
|
assert(!result.match("ab").isSuccess)
|
||||||
"b".shouldNotMatch()
|
assert(result.match("acd").isSuccess)
|
||||||
}
|
assert(result.match("abbbd").isSuccess)
|
||||||
|
assert(!result.match("a").isSuccess)
|
||||||
|
assert(!result.match("b").isSuccess)
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun testAndThen() {
|
fun testAndThen() {
|
||||||
checkRegex("ab") {
|
val input = "ab"
|
||||||
"ab".shouldMatch()
|
val parser = RegexParser()
|
||||||
"a".shouldNotMatch()
|
val result = parser.parseToEnd(input)
|
||||||
"b".shouldNotMatch()
|
assertEquals("ab", result.toString())
|
||||||
}
|
assert(result.match("ab").isSuccess)
|
||||||
|
assert(!result.match("a").isSuccess)
|
||||||
|
assert(!result.match("b").isSuccess)
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun testDotAndPlus() {
|
fun testDotAndPlus() {
|
||||||
checkRegex(".+a") {
|
val input = ".+a";
|
||||||
"a".shouldNotMatch()
|
val parser = RegexParser()
|
||||||
"ba".shouldMatch()
|
val result = parser.parseToEnd(input)
|
||||||
"bca".shouldMatch()
|
assertEquals(".+a", result.toString())
|
||||||
}
|
assert(!result.match("a").isSuccess)
|
||||||
}
|
assert(result.match("ba").isSuccess)
|
||||||
@Test
|
assert(result.match("bca").isSuccess)
|
||||||
fun testEscapedCharacter() {
|
|
||||||
checkRegex("\\+") {
|
|
||||||
"+".shouldMatch()
|
|
||||||
"a".shouldNotMatch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Test
|
|
||||||
fun testBracketContent() {
|
|
||||||
checkRegex("[abc]") {
|
|
||||||
"a".shouldMatch()
|
|
||||||
"b".shouldMatch()
|
|
||||||
"c".shouldMatch()
|
|
||||||
"d".shouldNotMatch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Test
|
|
||||||
fun testNestedGroups() {
|
|
||||||
checkRegex("(a(b|c)d)+") {
|
|
||||||
"ad".shouldNotMatch()
|
|
||||||
"abd".shouldMatch()
|
|
||||||
"acd".shouldMatch()
|
|
||||||
"a".shouldNotMatch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Test
|
|
||||||
fun testAnchorOperators() {
|
|
||||||
checkRegex("^abc$") {
|
|
||||||
"abc".shouldMatch()
|
|
||||||
"ab".shouldNotMatch()
|
|
||||||
"abcd".shouldNotMatch()
|
|
||||||
"xabc".shouldNotMatch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Test
|
|
||||||
fun testCaptureGroups() {
|
|
||||||
val input = "(a)(b)"
|
|
||||||
val result = compileRegex(input)
|
|
||||||
assertEquals("(a)(b)", result.toString())
|
|
||||||
val matchResult = result.find("ab")
|
|
||||||
assertNotNull(matchResult, "Expected match result to be non-null")
|
|
||||||
assertEquals("ab", matchResult.matched)
|
|
||||||
assertEquals(2, matchResult.captures.size)
|
|
||||||
assertEquals("a", matchResult.captures["0"])
|
|
||||||
assertEquals("b", matchResult.captures["1"])
|
|
||||||
}
|
|
||||||
@Test
|
|
||||||
fun testMatchAll() {
|
|
||||||
val input = "a+"
|
|
||||||
val regex = compileRegex(input)
|
|
||||||
val matches = regex.findAll("aaabaaa")
|
|
||||||
val results = matches.toList()
|
|
||||||
println("Matches found: ${results}")
|
|
||||||
// assertEquals(2, results.size)
|
|
||||||
assertEquals("aaa", results[0].matched)
|
|
||||||
assertEquals("aaa", results[1].matched)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue