feat: 引用を処理できるように
This commit is contained in:
parent
8d2e2246cb
commit
f3d3a9fb09
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue