Compare commits
10 commits
7ae97961b2
...
c7926c1f4d
Author | SHA1 | Date | |
---|---|---|---|
c7926c1f4d | |||
4f7cfae72d | |||
b7b71fb09d | |||
34052d1ea2 | |||
fb1cf96fed | |||
f143aba629 | |||
45150236c3 | |||
5536b872b4 | |||
78472511e7 | |||
60ec5916d0 |
3 changed files with 329 additions and 198 deletions
|
@ -2,16 +2,45 @@ package org.example
|
|||
|
||||
import com.github.h0tk3y.betterParse.combinators.*
|
||||
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.lexer.literalToken
|
||||
import com.github.h0tk3y.betterParse.lexer.regexToken
|
||||
import com.github.h0tk3y.betterParse.lexer.token
|
||||
import com.github.h0tk3y.betterParse.parser.Parser
|
||||
|
||||
class RegexParser : Grammar<RegexItem>() {
|
||||
private var groupCounter = 0
|
||||
|
||||
val escapedCharacter by regexToken("\\\\[+*?.$^()|\\[\\]]")
|
||||
val postfixOperator by regexToken("[+*?]")
|
||||
val anchorOperator by regexToken("[$^]")
|
||||
val alternationSymbol by literalToken("|")
|
||||
val openParenSymbol 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 charToken by regexToken("[a-zA-Z0-9]")
|
||||
|
@ -20,22 +49,25 @@ class RegexParser : Grammar<RegexItem>() {
|
|||
|
||||
val item: Parser<RegexItem> by
|
||||
char or
|
||||
(anchorOperator map { AnchorItem(it.text) }) or
|
||||
(dot asJust DotItem()) or
|
||||
(skip(openParenSymbol) and (parser(::rootParser)) and skip(closeParenSymbol))
|
||||
(escapedCharacter map { CharItem(it.text.substring(1)) }) or
|
||||
(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
|
||||
(item and optional(postfixOperator)) map
|
||||
{ result ->
|
||||
result.t1.let { first ->
|
||||
result.t2?.let {
|
||||
when (it.text) {
|
||||
"+" -> PlusItem(first)
|
||||
"*" -> StarItem(first)
|
||||
"?" -> QuestionItem(first)
|
||||
else -> first
|
||||
}
|
||||
}
|
||||
?: first
|
||||
(item and optional(postfixOperator)) map { (item, op) ->
|
||||
when (op?.text) {
|
||||
"+" -> PlusItem(item)
|
||||
"*" -> StarItem(item)
|
||||
"?" -> QuestionItem(item)
|
||||
else -> item
|
||||
}
|
||||
}
|
||||
val andThen: Parser<RegexItem> by
|
||||
|
@ -48,3 +80,7 @@ class RegexParser : Grammar<RegexItem>() {
|
|||
|
||||
override val rootParser: Parser<RegexItem> by termWithAlternation
|
||||
}
|
||||
|
||||
fun compileRegex(input: String): RegexItem {
|
||||
return RegexParser().parseToEnd(input)
|
||||
}
|
|
@ -1,175 +1,208 @@
|
|||
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()
|
||||
val input: String,
|
||||
val startIndex: Int,
|
||||
val endIndex: Int,
|
||||
val captures: Map<String, String> = emptyMap()
|
||||
) {
|
||||
val matched: String
|
||||
get() = input.substring(startIndex, endIndex)
|
||||
val remaining: String
|
||||
get() = input.substring(endIndex)
|
||||
}
|
||||
|
||||
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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias AvailableState = Sequence<State>
|
||||
|
||||
// 재귀 하향 분석기.
|
||||
// 백트랙킹 기반.
|
||||
interface RegexItem {
|
||||
override fun toString(): String
|
||||
fun findMatch(str: String): AvailableState
|
||||
fun findMatch(str: String, position: Int = 0): AvailableState
|
||||
}
|
||||
|
||||
fun RegexItem.match(item: String): MatchResult {
|
||||
// 기본 매칭 함수. AvailableState를 MatchResult로 변환
|
||||
return MatchResult(this.findMatch(item))
|
||||
fun RegexItem.findAll(item: String): Sequence<State> {
|
||||
return sequence {
|
||||
var position = 0
|
||||
// 문자열의 끝까지 반복합니다. 비어있어도 한번은 시도합니다.
|
||||
while (position <= item.length) {
|
||||
// findMatch 메서드를 호출하여 매칭을 시도합니다.
|
||||
val matchResult = findMatch(item, position).firstOrNull()
|
||||
if (matchResult == null) {
|
||||
// 매칭이 실패하면 position을 증가시키고 다시 시도합니다.
|
||||
position++
|
||||
continue
|
||||
}
|
||||
// 매칭이 성공하면 MatchResult를 생성하여 반환합니다.
|
||||
yield(matchResult)
|
||||
|
||||
// 다음 위치로 이동합니다.
|
||||
position =
|
||||
if (matchResult.startIndex == matchResult.endIndex) {
|
||||
position + 1
|
||||
} else {
|
||||
matchResult.endIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RegexItem.find(item: String): State? {
|
||||
// findAll 에서 첫 번째 매칭 결과를 반환합니다.
|
||||
return this.findAll(item).firstOrNull()
|
||||
}
|
||||
|
||||
fun RegexItem.containsMatchIn(item: String): Boolean {
|
||||
// 매칭 결과가 성공인지 확인하는 헬퍼 함수
|
||||
return this.find(item) != null
|
||||
}
|
||||
|
||||
class AndThenItem(val left: RegexItem, val right: RegexItem) : RegexItem {
|
||||
override fun toString(): String = "${left}${right}"
|
||||
override fun findMatch(str: String): AvailableState {
|
||||
val leftMatch = left.findMatch(str)
|
||||
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) {
|
||||
override fun findMatch(str: String, position: Int): AvailableState {
|
||||
val leftMatch = left.findMatch(str, position)
|
||||
return leftMatch.flatMap { leftState ->
|
||||
right.findMatch(str, leftState.endIndex).map { rightState ->
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
State(
|
||||
str,
|
||||
leftState.startIndex,
|
||||
rightState.endIndex,
|
||||
leftState.captures + rightState.captures // Combine captures
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CharItem(val value: String) : RegexItem {
|
||||
override fun toString(): String = value
|
||||
override fun findMatch(str: String): AvailableState {
|
||||
companion object {
|
||||
private val META_CHARS = setOf("\\", "+", "*", "?", ".", "(", ")", "|", "[", "]", "^", "$")
|
||||
}
|
||||
override fun toString(): String =
|
||||
if (value in META_CHARS) "\\$value" else value
|
||||
|
||||
override fun findMatch(str: String, position: Int): AvailableState {
|
||||
return when {
|
||||
// 첫번째 문자가 value와 일치하는지 확인
|
||||
str.isNotEmpty() && str[0].toString() == value -> {
|
||||
AvailableState(sequenceOf(State(value, str.substring(1))))
|
||||
position < str.length && str[position].toString() == value -> {
|
||||
sequenceOf(State(str, position, position + 1))
|
||||
}
|
||||
else -> AvailableState()
|
||||
else -> emptySequence() // 일치하지 않으면 빈 시퀀스 반환
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun matchMany(
|
||||
str: String,
|
||||
item: RegexItem,
|
||||
): Sequence<State> {
|
||||
class BracketItem(val content: String) : RegexItem {
|
||||
override fun toString(): String = "[$content]"
|
||||
|
||||
// TODO: 범위 처리
|
||||
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).seq.flatMap { state ->
|
||||
if (state.remaining.isEmpty()) {
|
||||
return item.findMatch(str, position).flatMap { state ->
|
||||
if (state.endIndex == str.length) {
|
||||
sequenceOf(state) // If remaining is empty, return the matched state
|
||||
} else {
|
||||
// Otherwise, continue matching with the remaining string
|
||||
matchMany(state.remaining, item).map { nextState ->
|
||||
State(state.matched + nextState.matched, nextState.remaining)
|
||||
matchMany(str, item, state.endIndex).map { nextState ->
|
||||
State(str, state.startIndex, nextState.endIndex)
|
||||
} + 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 {
|
||||
override fun toString(): String = "${item}+"
|
||||
override fun findMatch(str: String): AvailableState {
|
||||
return AvailableState(matchMany(str, item))
|
||||
override fun findMatch(str: String, position: Int): AvailableState {
|
||||
return matchMany(str, item, position)
|
||||
}
|
||||
}
|
||||
|
||||
class StarItem(val item: RegexItem) : RegexItem {
|
||||
override fun toString(): String = "${item}*"
|
||||
override fun findMatch(str: String): AvailableState {
|
||||
// *는 0개 이상의 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환
|
||||
val matchResult = this.item.findMatch(str)
|
||||
if (matchResult.isEmpty) {
|
||||
// If the item does not match, return an empty sequence
|
||||
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)
|
||||
}
|
||||
)
|
||||
override fun findMatch(str: String, position: Int): AvailableState {
|
||||
// *는 0번 또는 1번 이상 일치합니다.
|
||||
// 욕심쟁이(greedy) 방식으로 구현하기 위해, 가장 긴 매치(1번 이상)를 먼저 찾고, 그 다음에 0번 매치를 추가합니다.
|
||||
val oneOrMoreMatches = matchMany(str, item, position)
|
||||
val zeroMatch = sequenceOf(State(str, position, position))
|
||||
return oneOrMoreMatches + zeroMatch
|
||||
}
|
||||
}
|
||||
|
||||
class QuestionItem(val item: RegexItem) : RegexItem {
|
||||
override fun toString(): String = "${item}?"
|
||||
override fun findMatch(str: String): AvailableState {
|
||||
// ?는 0개 또는 1개 매칭을 의미하므로, 먼저 시도해보고 실패하면 빈 시퀀스를 반환
|
||||
val matchResult = this.item.findMatch(str)
|
||||
if (matchResult.isEmpty) {
|
||||
// If the item does not match, return an empty sequence
|
||||
return AvailableState(sequenceOf(State("", str)))
|
||||
}
|
||||
// If it matches, return the successful match
|
||||
return AvailableState(matchResult.map { State(it.matched, it.remaining) })
|
||||
override fun findMatch(str: String, position: Int): AvailableState {
|
||||
// ?는 0번 또는 1번 일치합니다.
|
||||
val oneMatch = item.findMatch(str, position)
|
||||
val zeroMatch = sequenceOf(State(str, position, position))
|
||||
// 1번 매치를 0번 매치보다 우선합니다.
|
||||
return oneMatch + zeroMatch
|
||||
}
|
||||
}
|
||||
|
||||
class DotItem : RegexItem {
|
||||
override fun toString(): String = "."
|
||||
override fun findMatch(str: String): AvailableState =
|
||||
override fun findMatch(str: String, position: Int): AvailableState =
|
||||
// .은 임의의 한 문자와 매칭되므로, 첫 문자가 존재하면 매칭 성공
|
||||
when {
|
||||
str.isNotEmpty() ->
|
||||
AvailableState(sequenceOf(State(str[0].toString(), str.substring(1))))
|
||||
else -> AvailableState() // 빈 문자열에 대해서는 매칭 실패
|
||||
position < str.length -> sequenceOf(State(str, position, position + 1))
|
||||
else -> emptySequence() // 빈 문자열에 대해서는 매칭 실패
|
||||
}
|
||||
}
|
||||
|
||||
class AlternationItem(val left: RegexItem, val right: RegexItem) : RegexItem {
|
||||
override fun toString(): String = "(${left}|${right})"
|
||||
override fun findMatch(str: String): AvailableState {
|
||||
override fun toString(): String = "${left}|${right}"
|
||||
override fun findMatch(str: String, position: Int): AvailableState {
|
||||
// Alternation은 왼쪽 또는 오른쪽 항목 중 하나와 매칭되므로, 각각 시도해보고 성공하는 경우를 반환
|
||||
val leftMatch = left.findMatch(str)
|
||||
val rightMatch = right.findMatch(str)
|
||||
val leftMatch = left.findMatch(str, position)
|
||||
val rightMatch = right.findMatch(str, position)
|
||||
|
||||
return AvailableState(
|
||||
(leftMatch + rightMatch) // 두 매칭 결과를 합쳐서 반환
|
||||
)
|
||||
return leftMatch + rightMatch // 두 매칭 결과를 합쳐서 반환
|
||||
}
|
||||
}
|
|
@ -2,109 +2,171 @@ package org.example
|
|||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import com.github.h0tk3y.betterParse.grammar.parseToEnd
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
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 {
|
||||
|
||||
@Test
|
||||
fun testSimpleCharacter() {
|
||||
val input = "a"
|
||||
val result = RegexParser().parseToEnd(input)
|
||||
assertEquals("a",result.toString())
|
||||
checkRegex("a") {
|
||||
"a".shouldMatch()
|
||||
"b".shouldNotMatch()
|
||||
"".shouldNotMatch()
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testCharacterWithPlus() {
|
||||
val input = "a+"
|
||||
val parser = RegexParser()
|
||||
val result = parser.parseToEnd(input)
|
||||
assertEquals("a+", result.toString())
|
||||
assert(result.match("a").isSuccess)
|
||||
assert(result.match("aa").isSuccess)
|
||||
assert(!result.match("b").isSuccess)
|
||||
checkRegex("a+") {
|
||||
"a".shouldMatch()
|
||||
"aa".shouldMatch()
|
||||
"b".shouldNotMatch()
|
||||
"".shouldNotMatch()
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testCharacterWithStar() {
|
||||
val input = "b*"
|
||||
val parser = RegexParser()
|
||||
val result = parser.parseToEnd(input)
|
||||
assertEquals("b*", result.toString())
|
||||
assert(result.match("").isSuccess)
|
||||
assert(result.match("b").isSuccess)
|
||||
assert(result.match("bb").isSuccess)
|
||||
assert(result.match("a").isSuccess)
|
||||
checkRegex("b*") {
|
||||
"b".shouldMatch()
|
||||
"bb".shouldMatch()
|
||||
"".shouldMatch() // 빈 문자열도 매칭됨
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testCharacterWithQuestion() {
|
||||
val input = "c?"
|
||||
val parser = RegexParser()
|
||||
val result = parser.parseToEnd(input)
|
||||
assertEquals("c?", result.toString())
|
||||
assert(result.match("").isSuccess)
|
||||
assert(result.match("c").isSuccess)
|
||||
assert(result.match("cc").isSuccess)
|
||||
checkRegex("c?") {
|
||||
"c".shouldMatch()
|
||||
"".shouldMatch() // 빈 문자열도 매칭됨
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testDot() {
|
||||
val input = "."
|
||||
val parser = RegexParser()
|
||||
val result = parser.parseToEnd(input)
|
||||
assertEquals(".", result.toString())
|
||||
assert(result.match("a").isSuccess)
|
||||
assert(result.match("1").isSuccess)
|
||||
assert(!result.match("").isSuccess)
|
||||
checkRegex(".") {
|
||||
"a".shouldMatch()
|
||||
"1".shouldMatch()
|
||||
"".shouldNotMatch() // 빈 문자열은 매칭되지 않음
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAlternation() {
|
||||
val input = "a|b"
|
||||
val parser = RegexParser()
|
||||
val result = parser.parseToEnd(input)
|
||||
assertEquals("(a|b)", result.toString())
|
||||
assert(result.match("a").isSuccess)
|
||||
assert(result.match("b").isSuccess)
|
||||
assert(!result.match("c").isSuccess)
|
||||
checkRegex("a|b") {
|
||||
"a".shouldMatch()
|
||||
"b".shouldMatch()
|
||||
"c".shouldNotMatch()
|
||||
"".shouldNotMatch() // 빈 문자열은 매칭되지 않음
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testParentheses() {
|
||||
val input = "(d)"
|
||||
val parser = RegexParser()
|
||||
val result = parser.parseToEnd(input)
|
||||
assertEquals("d", result.toString())
|
||||
assert(result.match("d").isSuccess)
|
||||
assert(!result.match("e").isSuccess)
|
||||
checkRegex("(d)") {
|
||||
"d".shouldMatch()
|
||||
"e".shouldNotMatch()
|
||||
"".shouldNotMatch() // 빈 문자열은 매칭되지 않음
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testComplexExpression() {
|
||||
val input = "a(b|c)*d+"
|
||||
val parser = RegexParser()
|
||||
val result = parser.parseToEnd(input)
|
||||
assertEquals("a(b|c)*d+", result.toString())
|
||||
assert(result.match("ad").isSuccess)
|
||||
assert(!result.match("ab").isSuccess)
|
||||
assert(result.match("acd").isSuccess)
|
||||
assert(result.match("abbbd").isSuccess)
|
||||
assert(!result.match("a").isSuccess)
|
||||
assert(!result.match("b").isSuccess)
|
||||
checkRegex("a(b|c)*d+") {
|
||||
"ad".shouldMatch()
|
||||
"ab".shouldNotMatch()
|
||||
"acd".shouldMatch()
|
||||
"abbbd".shouldMatch()
|
||||
"a".shouldNotMatch()
|
||||
"b".shouldNotMatch()
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testAndThen() {
|
||||
val input = "ab"
|
||||
val parser = RegexParser()
|
||||
val result = parser.parseToEnd(input)
|
||||
assertEquals("ab", result.toString())
|
||||
assert(result.match("ab").isSuccess)
|
||||
assert(!result.match("a").isSuccess)
|
||||
assert(!result.match("b").isSuccess)
|
||||
checkRegex("ab") {
|
||||
"ab".shouldMatch()
|
||||
"a".shouldNotMatch()
|
||||
"b".shouldNotMatch()
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testDotAndPlus() {
|
||||
val input = ".+a";
|
||||
val parser = RegexParser()
|
||||
val result = parser.parseToEnd(input)
|
||||
assertEquals(".+a", result.toString())
|
||||
assert(!result.match("a").isSuccess)
|
||||
assert(result.match("ba").isSuccess)
|
||||
assert(result.match("bca").isSuccess)
|
||||
checkRegex(".+a") {
|
||||
"a".shouldNotMatch()
|
||||
"ba".shouldMatch()
|
||||
"bca".shouldMatch()
|
||||
}
|
||||
}
|
||||
@Test
|
||||
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