From 78472511e7ac76f63d7b4b115e840e3edf52eae0 Mon Sep 17 00:00:00 2001 From: monoid Date: Sun, 29 Jun 2025 13:45:05 +0900 Subject: [PATCH] feat: add bracket content parsing and corresponding tests --- lib/src/main/kotlin/org/example/Parser.kt | 29 +++++++++++++++++-- lib/src/main/kotlin/org/example/RegexItem.kt | 15 ++++++++++ lib/src/test/kotlin/org/example/ParserTest.kt | 11 +++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/src/main/kotlin/org/example/Parser.kt b/lib/src/main/kotlin/org/example/Parser.kt index 1f4d122..bceac9f 100644 --- a/lib/src/main/kotlin/org/example/Parser.kt +++ b/lib/src/main/kotlin/org/example/Parser.kt @@ -5,6 +5,7 @@ import com.github.h0tk3y.betterParse.grammar.Grammar 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() { @@ -14,9 +15,30 @@ class RegexParser : Grammar() { val alternationSymbol by literalToken("|") val openParenSymbol by literalToken("(") val closeParenSymbol by literalToken(")") - val bracketOpen by literalToken("[") - val bracketClose 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]") @@ -26,6 +48,7 @@ class RegexParser : Grammar() { char or (dot asJust DotItem()) or (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)) val term: Parser by diff --git a/lib/src/main/kotlin/org/example/RegexItem.kt b/lib/src/main/kotlin/org/example/RegexItem.kt index 4806bce..a85fff0 100644 --- a/lib/src/main/kotlin/org/example/RegexItem.kt +++ b/lib/src/main/kotlin/org/example/RegexItem.kt @@ -92,6 +92,21 @@ class CharItem(val value: String) : RegexItem { } } +class BracketItem(val content: String) : RegexItem { + override fun toString(): String = "[$content]" + + // TODO: 범위 처리 + override fun findMatch(str: String): AvailableState { + // 대괄호 안의 내용과 일치하는 첫 문자를 찾음 + return when { + str.isNotEmpty() && content.contains(str[0]) -> { + AvailableState(sequenceOf(State(str[0].toString(), str.substring(1)))) + } + else -> AvailableState() + } + } +} + fun matchMany( str: String, item: RegexItem, diff --git a/lib/src/test/kotlin/org/example/ParserTest.kt b/lib/src/test/kotlin/org/example/ParserTest.kt index 263488b..9183754 100644 --- a/lib/src/test/kotlin/org/example/ParserTest.kt +++ b/lib/src/test/kotlin/org/example/ParserTest.kt @@ -116,4 +116,15 @@ class ParserTest { assert(result.match("+").isSuccess) assert(!result.match("a").isSuccess) } + @Test + fun testBracketContent() { + val input = "[abc]" + val parser = RegexParser() + val result = parser.parseToEnd(input) + assertEquals("[abc]", result.toString()) + assert(result.match("a").isSuccess) + assert(result.match("b").isSuccess) + assert(result.match("c").isSuccess) + assert(!result.match("d").isSuccess) + } } \ No newline at end of file