feat: リストを追加
This commit is contained in:
parent
f14934891a
commit
9605c7d7aa
|
@ -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() }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue