feat: リストを追加

This commit is contained in:
usbharu 2024-11-17 19:01:16 +09:00
parent f14934891a
commit 9605c7d7aa
Signed by: usbharu
GPG Key ID: 95CBCF7046307B77
3 changed files with 192 additions and 8 deletions

View File

@ -1,6 +1,7 @@
package dev.usbharu.markdown package dev.usbharu.markdown
import kotlin.js.JsExport import kotlin.js.JsExport
import kotlin.js.JsName
@JsExport @JsExport
sealed class AstNode { sealed class AstNode {
@ -18,12 +19,15 @@ sealed class AstNode {
override fun print(): String { override fun print(): String {
return body.joinToString("") { return body.joinToString("") {
if (it is BlockNode) { if (it is BlockNode) {
it.print() + "\n" it.print() + "\n\n"
} else { } else {
it.print() it.print()
} }
} }
} }
@JsName("with")
constructor(vararg nodes: AstNode) : this(nodes.toList())
} }
sealed class BlockNode : AstNode() sealed class BlockNode : AstNode()
@ -70,16 +74,58 @@ sealed class AstNode {
} }
} }
sealed class ListNode : BlockNode() sealed interface ListableNode {
data class DiscListNode(val node: InlineNode, val childList: List<ListNode>) : ListNode() fun print(): String
data class DecimalListNode(val node: InlineNode, val childList: List<ListNode>) : ListNode() }
data class ParagraphNode(val nodes: List<InlineNode>) : ListNode() {
sealed class ListNode(open val itemNode: List<ListItemNode>) : BlockNode(), ListableNode {
override fun print(): String {
return nestedPrint(0)
}
fun nestedPrint(nest: Int): String {
val builder = StringBuilder()
for (node in itemNode) {
builder.append(" ".repeat(nest)).append("- ").append(node.nestedPrint(nest)).append("\n")
}
return builder.toString().trim('\n')
}
}
data class DecimalListNode(override val itemNode: List<ListItemNode>) : ListNode(itemNode)
data class DiscListNode(override val itemNode: List<ListItemNode>) : ListNode(itemNode) {
@JsName("with")
constructor(vararg nodes: ListItemNode) : this(nodes.toList())
}
data class ListItemNode(val nodes: MutableList<ListableNode>) : BlockNode() {
override fun print(): String {
return nestedPrint(0)
}
fun nestedPrint(nest: Int): String {
val builder = StringBuilder()
for (node in nodes) {
if (node is ListNode) {
builder.append("\n").append(node.nestedPrint(nest + 1))
} else {
builder.append(node.print())
}
}
return builder.toString()
}
@JsName("with")
constructor(vararg nodes: ListableNode) : this(nodes.toMutableList())
}
data class ParagraphNode(val nodes: List<InlineNode>) : BlockNode() {
override fun print(): String { override fun print(): String {
return nodes.joinToString("\n") { it.print() } return nodes.joinToString("\n") { it.print() }
} }
} }
sealed class InlineNode : AstNode(), QuotableNode sealed class InlineNode : AstNode(), QuotableNode, ListableNode
data class InlineNodes(val nodes: MutableList<InlineNode>) : InlineNode() { data class InlineNodes(val nodes: MutableList<InlineNode>) : InlineNode() {
override fun print(): String { override fun print(): String {
return nodes.joinToString("") { it.print() } return nodes.joinToString("") { it.print() }

View File

@ -2,6 +2,8 @@ package dev.usbharu.markdown
import dev.usbharu.markdown.AstNode.* import dev.usbharu.markdown.AstNode.*
import dev.usbharu.markdown.Token.* import dev.usbharu.markdown.Token.*
import dev.usbharu.markdown.Token.List.ListType.DECIMAL
import dev.usbharu.markdown.Token.List.ListType.DISC
import kotlin.collections.List import kotlin.collections.List
import kotlin.js.JsExport import kotlin.js.JsExport
@ -27,10 +29,10 @@ class Parser {
is CodeBlockLanguage -> TODO() is CodeBlockLanguage -> TODO()
is Header -> header(next, iterator) is Header -> header(next, iterator)
is Html -> TODO() is Html -> TODO()
is Token.List -> TODO() is Token.List -> list(next, iterator)
is Quote -> quote(next, iterator) is Quote -> quote(next, iterator)
is Separator -> separator(next, iterator) is Separator -> separator(next, iterator)
InQuoteBreak -> TODO() InQuoteBreak -> null
} }
if (node != null) { if (node != null) {
nodes.add(node) nodes.add(node)
@ -39,6 +41,55 @@ class Parser {
return RootNode(BodyNode(nodes)) return RootNode(BodyNode(nodes))
} }
fun list(list: Token.List, iterator: PeekableTokenIterator): ListNode {
return internalList(list, iterator, 1)
}
fun internalList(list: Token.List, iterator: PeekableTokenIterator, currentNest: Int): ListNode {
val listItems = mutableListOf<ListItemNode>()
list@ while (iterator.hasNext() && (iterator.peekOrNull() is Token.List || isInline(iterator.peekOrNull()))) {
val item = mutableListOf<ListableNode>()
listItem@ while (isInline(iterator.peekOrNull()) && iterator.peekOrNull() !is Token.List && iterator.peekOrNull() !is LineBreak) {
val next = iterator.next()
val inline = inline(next, iterator)
println("internalList inline: " + inline)
item.addAll(inline)
}
while (iterator.peekOrNull() is LineBreak) {
iterator.skip()
}
val count =
if (iterator.peekOrNull() is Whitespace) {
val whitespace = iterator.next() as Whitespace
whitespace.count
} else {
1
}
println("count = $count,currentNest = $currentNest,peek = ${iterator.peekOrNull()}")
if (currentNest < count && iterator.peekOrNull() is Token.List) {
item.add(internalList(iterator.next() as Token.List, iterator, count))
}
if (currentNest > count && iterator.peekOrNull() is Token.List) {
iterator.skip()
if (item.isNotEmpty()) {
listItems.add(ListItemNode(item))
}
break
}
while (iterator.peekOrNull() is Token.List) {
iterator.skip()
}
if (item.isNotEmpty()) {
listItems.add(ListItemNode(item))
}
}
println("end $currentNest")
return when (list.type) {
DISC -> DiscListNode(listItems)
DECIMAL -> DecimalListNode(listItems)
}
}
tailrec fun addQuote(quoteNode: QuoteNode, quotableNode: List<QuotableNode>, nest: Int) { tailrec fun addQuote(quoteNode: QuoteNode, quotableNode: List<QuotableNode>, nest: Int) {
if (nest == 1) { if (nest == 1) {
quoteNode.nodes.addAll(quotableNode) quoteNode.nodes.addAll(quotableNode)

View File

@ -534,4 +534,91 @@ class ParserTest {
) )
} }
@Test
fun list() {
val parser = Parser()
val actual = parser.parse(
listOf(DiscList, Text("aiueo"), Whitespace(1, ' '), Text("aa"), LineBreak(1), DiscList, Text("abcd"))
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
DiscListNode(
listOf(
ListItemNode(
mutableListOf(
PlainText("aiueo"),
PlainText(" "),
PlainText("aa")
)
),
ListItemNode(
mutableListOf(
PlainText("abcd")
)
)
)
)
)
)
), actual
)
}
@Test
fun listネスト() {
val parser = Parser()
val actual = parser.parse(
listOf(
DiscList,
Text("aiueo"),
LineBreak(1),
Whitespace(4, ' '),
DiscList,
Text("abcd"),
LineBreak(1),
Whitespace(4, ' '),
DiscList,
Text("efgh"),
LineBreak(1),
DiscList,
Text("hoge")
)
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
DiscListNode(
ListItemNode(
PlainText("aiueo"),
DiscListNode(
ListItemNode(
PlainText("abcd"),
),
ListItemNode(
PlainText("efgh"),
)
)
),
ListItemNode(
PlainText("hoge")
)
)
)
), actual
)
}
} }