feat: 引用を処理できるように

This commit is contained in:
usbharu 2024-11-17 01:22:35 +09:00
parent 8d2e2246cb
commit f3d3a9fb09
Signed by: usbharu
GPG Key ID: 95CBCF7046307B77
6 changed files with 291 additions and 8 deletions

View File

@ -16,7 +16,13 @@ sealed class AstNode {
data class BodyNode(val body: List<AstNode>) : AstNode() {
override fun print(): String {
return body.joinToString("\n") { it.print() }
return body.joinToString("") {
if (it is BlockNode) {
it.print() + "\n"
} else {
it.print()
}
}
}
}
@ -34,8 +40,30 @@ sealed class AstNode {
}
}
sealed interface QuotableNode
data class QuoteNode(val nodes: List<QuotableNode>) : AstNode(), QuotableNode
sealed interface QuotableNode {
fun print(): String
}
data class QuoteNode(val nodes: MutableList<QuotableNode>) : AstNode(), QuotableNode {
override fun print(): String {
return printNest(1)
}
fun printNest(nest: Int): String {
val builder = StringBuilder()
for (node in nodes) {
if (node is QuoteNode) {
builder.append(node.printNest(nest + 1))
} else if (node is BreakNode) {
builder.append(node.print())
} else {
builder.append(">".repeat(nest)).append(' ').append(node.print())
}
}
return builder.toString()
}
}
data object SeparatorNode : BlockNode() {
override fun print(): String {
return "---"
@ -57,6 +85,7 @@ sealed class AstNode {
return nodes.joinToString("") { it.print() }
}
}
data class ItalicNode(val nodes: MutableList<InlineNode>) : InlineNode() {
override fun print(): String {
return nodes.joinToString("", prefix = "*", postfix = "*") { it.print() }
@ -118,5 +147,11 @@ sealed class AstNode {
return url
}
}
data object BreakNode : InlineNode() {
override fun print(): String {
return "\n"
}
}
}

View File

@ -89,7 +89,7 @@ class Lexer {
} else if (htmlNest != 0) {
codeBuffer.append(" ")
} else {
addBreak(tokens)
addBreak(tokens, inQuote)
}
}
inQuote = false
@ -107,6 +107,9 @@ class Lexer {
tokens.removeLast()
tokens.add(LineBreak(1))
}
if (lastToken is InQuoteBreak) {
tokens.removeLast()
}
return tokens
}
@ -527,7 +530,11 @@ class Lexer {
}
}
fun addBreak(tokens: MutableList<Token>) {
fun addBreak(tokens: MutableList<Token>, inQuote: Boolean = false) {
if (inQuote) {
tokens.add(InQuoteBreak)
return
}
val lastOrNull = tokens.lastOrNull()
if (lastOrNull is LineBreak && 1 <= lastOrNull.count) {
tokens.removeLast()

View File

@ -28,8 +28,9 @@ class Parser {
is Header -> header(next, iterator)
is Html -> TODO()
is Token.List -> TODO()
is Quote -> TODO()
is Quote -> quote(next, iterator)
is Separator -> separator(next, iterator)
InQuoteBreak -> TODO()
}
if (node != null) {
nodes.add(node)
@ -38,6 +39,44 @@ class Parser {
return RootNode(BodyNode(nodes))
}
tailrec fun addQuote(quoteNode: QuoteNode, quotableNode: List<QuotableNode>, nest: Int) {
if (nest == 1) {
quoteNode.nodes.addAll(quotableNode)
return
}
addQuote(quoteNode.nodes.findLast { it is QuoteNode } as QuoteNode, quotableNode, nest.dec())
}
fun quote(quote: Quote, iterator: PeekableTokenIterator): QuoteNode {
var quote2 = quote
var maxNest = quote.count
val quoteNode = createNest(maxNest)
while (true) {
val list = mutableListOf<QuotableNode>()
while (isInline(iterator.peekOrNull()) && iterator.peekOrNull() !is InQuoteBreak) {
println("next token: " + iterator.peekOrNull())
list.addAll(inline(iterator.next(), iterator))
}
if (iterator.peekOrNull() is InQuoteBreak) {
list.add(BreakNode)
iterator.skip()
}
if (maxNest < quote2.count) {
addQuote(quoteNode, mutableListOf(createNest(quote2.count - maxNest)), maxNest)
maxNest = quote2.count
}
addQuote(quoteNode, list, quote2.count)
if (iterator.peekOrNull() is Quote) {
quote2 = iterator.next() as Quote
} else {
break
}
}
return quoteNode
}
fun separator(separator: Separator, iterator: PeekableTokenIterator): AstNode {
return SeparatorNode
}
@ -74,7 +113,7 @@ class Parser {
return when (token) {
is Asterisk, is InlineCodeBlock, is Strike,
is Text, is Whitespace, Exclamation, ParenthesesEnd, ParenthesesStart,
SquareBracketStart, SquareBracketEnd, is Url, is UrlTitle, is LineBreak -> true
SquareBracketStart, SquareBracketEnd, is Url, is UrlTitle, is LineBreak, is InQuoteBreak -> true
else -> false
}
@ -97,7 +136,10 @@ class Parser {
is UrlTitle -> PlainText("\"${token.title}\"")
is Whitespace -> whitespace(token, iterator)
is LineBreak -> null
else -> TODO()
else -> {
println("error" + token)
TODO()
}
}
if (node != null) {
@ -271,4 +313,20 @@ class Parser {
iterator.print()
return null
}
companion object {
fun createNest(nest: Int, quoteNode: QuoteNode = QuoteNode(mutableListOf())): QuoteNode {
createNest2(nest, quoteNode, quoteNode)
return quoteNode
}
tailrec fun createNest2(nest: Int, current: QuoteNode, quoteNode: QuoteNode): QuoteNode {
if (nest == 1) {
return quoteNode
}
val element = QuoteNode(mutableListOf())
current.nodes.add(element)
return createNest2(nest - 1, element, quoteNode)
}
}
}

View File

@ -7,6 +7,7 @@ sealed class Token {
data class Text(var text: String) : Token()
data class LineBreak(var count: Int) : Token()
data object BlockBreak : Token()
data object InQuoteBreak : Token()
data class Header(var count: Int) : Token()
data class Quote(var count: Int) : Token()
data class Separator(var count: Int, val char: Char) : Token()

View File

@ -219,6 +219,30 @@ class LexerTest {
assertContentEquals(listOf(Quote(2), Text(">abcd")), actual)
}
@Test
fun 引用複数行() {
val lexer = Lexer()
val actual = lexer.lex("> aiueo\n>> >abcd\n> hoge\nfuga")
println(actual)
assertContentEquals(
listOf(
Quote(1),
Text("aiueo"),
InQuoteBreak,
Quote(2),
Text(">abcd"),
InQuoteBreak,
Quote(1),
Text("hoge"),
InQuoteBreak,
Text("fuga")
), actual
)
}
@Test
fun セパレーター() {
val lexer = Lexer()

View File

@ -343,4 +343,162 @@ class ParserTest {
), actual
)
}
@Test
fun createNest() {
val quoteNode = QuoteNode(mutableListOf(PlainText("aa")))
println(Parser.createNest(3, quoteNode))
println(quoteNode)
}
@Test
fun createNest2() {
val quoteNode = QuoteNode(mutableListOf(PlainText("aaa")))
Parser.createNest2(3, quoteNode, quoteNode)
println(quoteNode)
}
@Test
fun quote() {
val parser = Parser()
val actual = parser.parse(
listOf(
Quote(1), Text("a")
)
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
QuoteNode(
mutableListOf(
PlainText("a")
)
)
)
)
), actual
)
}
@Test
fun quote2() {
val parser = Parser()
val actual = parser.parse(
listOf(
Quote(2), Text("a")
)
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
QuoteNode(
mutableListOf(
QuoteNode(
mutableListOf(
PlainText("a")
)
)
)
)
)
)
), actual
)
}
@Test
fun quote3() {
val parser = Parser()
val actual = parser.parse(
listOf(
Quote(2), Text("a"), InQuoteBreak, Quote(2), Text("abcd")
)
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
QuoteNode(
mutableListOf(
QuoteNode(
mutableListOf(
PlainText("a"), BreakNode, PlainText("abcd")
)
)
)
)
)
)
), actual
)
}
@Test
fun quote4() {
val parser = Parser()
val actual = parser.parse(
listOf(
Quote(1),
Text("aiueo"),
InQuoteBreak,
Quote(2),
Text(">abcd"),
InQuoteBreak,
Quote(1),
Text("hoge"),
InQuoteBreak,
Text("fuga")
)
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
QuoteNode(
mutableListOf(
PlainText("aiueo"),
BreakNode,
QuoteNode(
mutableListOf(
PlainText(">abcd"),
BreakNode
)
),
PlainText("hoge"),
BreakNode
)
),
ParagraphNode(
mutableListOf(
PlainText("fuga")
)
)
)
)
), actual
)
}
}