diff --git a/library/src/commonMain/kotlin/dev/usbharu/markdown/AstNode.kt b/library/src/commonMain/kotlin/dev/usbharu/markdown/AstNode.kt index 933e8ff..0fc95ff 100644 --- a/library/src/commonMain/kotlin/dev/usbharu/markdown/AstNode.kt +++ b/library/src/commonMain/kotlin/dev/usbharu/markdown/AstNode.kt @@ -1,6 +1,7 @@ package dev.usbharu.markdown import kotlin.js.JsExport +import kotlin.js.JsName @JsExport sealed class AstNode { @@ -18,12 +19,15 @@ sealed class AstNode { override fun print(): String { return body.joinToString("") { if (it is BlockNode) { - it.print() + "\n" + it.print() + "\n\n" } else { it.print() } } } + + @JsName("with") + constructor(vararg nodes: AstNode) : this(nodes.toList()) } sealed class BlockNode : AstNode() @@ -70,16 +74,58 @@ sealed class AstNode { } } - sealed class ListNode : BlockNode() - data class DiscListNode(val node: InlineNode, val childList: List) : ListNode() - data class DecimalListNode(val node: InlineNode, val childList: List) : ListNode() - data class ParagraphNode(val nodes: List) : ListNode() { + sealed interface ListableNode { + fun print(): String + } + + sealed class ListNode(open val itemNode: List) : 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) : ListNode(itemNode) + data class DiscListNode(override val itemNode: List) : ListNode(itemNode) { + @JsName("with") + constructor(vararg nodes: ListItemNode) : this(nodes.toList()) + } + + data class ListItemNode(val nodes: MutableList) : 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) : BlockNode() { override fun print(): String { return nodes.joinToString("\n") { it.print() } } } - sealed class InlineNode : AstNode(), QuotableNode + sealed class InlineNode : AstNode(), QuotableNode, ListableNode data class InlineNodes(val nodes: MutableList) : InlineNode() { override fun print(): String { return nodes.joinToString("") { it.print() } diff --git a/library/src/commonMain/kotlin/dev/usbharu/markdown/Parser.kt b/library/src/commonMain/kotlin/dev/usbharu/markdown/Parser.kt index 76a7dbe..c92b067 100644 --- a/library/src/commonMain/kotlin/dev/usbharu/markdown/Parser.kt +++ b/library/src/commonMain/kotlin/dev/usbharu/markdown/Parser.kt @@ -2,6 +2,8 @@ package dev.usbharu.markdown import dev.usbharu.markdown.AstNode.* 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.js.JsExport @@ -27,10 +29,10 @@ class Parser { is CodeBlockLanguage -> TODO() is Header -> header(next, iterator) is Html -> TODO() - is Token.List -> TODO() + is Token.List -> list(next, iterator) is Quote -> quote(next, iterator) is Separator -> separator(next, iterator) - InQuoteBreak -> TODO() + InQuoteBreak -> null } if (node != null) { nodes.add(node) @@ -39,6 +41,55 @@ class Parser { 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() + list@ while (iterator.hasNext() && (iterator.peekOrNull() is Token.List || isInline(iterator.peekOrNull()))) { + val item = mutableListOf() + 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, nest: Int) { if (nest == 1) { quoteNode.nodes.addAll(quotableNode) diff --git a/library/src/commonTest/kotlin/dev/usbharu/markdown/ParserTest.kt b/library/src/commonTest/kotlin/dev/usbharu/markdown/ParserTest.kt index 2f1d0fe..977317c 100644 --- a/library/src/commonTest/kotlin/dev/usbharu/markdown/ParserTest.kt +++ b/library/src/commonTest/kotlin/dev/usbharu/markdown/ParserTest.kt @@ -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 + ) + } } \ No newline at end of file