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() {
|
data class BodyNode(val body: List<AstNode>) : AstNode() {
|
||||||
override fun print(): String {
|
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
|
sealed interface QuotableNode {
|
||||||
data class QuoteNode(val nodes: List<QuotableNode>) : AstNode(), 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() {
|
data object SeparatorNode : BlockNode() {
|
||||||
override fun print(): String {
|
override fun print(): String {
|
||||||
return "---"
|
return "---"
|
||||||
|
@ -57,6 +85,7 @@ sealed class AstNode {
|
||||||
return nodes.joinToString("") { it.print() }
|
return nodes.joinToString("") { it.print() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ItalicNode(val nodes: MutableList<InlineNode>) : InlineNode() {
|
data class ItalicNode(val nodes: MutableList<InlineNode>) : InlineNode() {
|
||||||
override fun print(): String {
|
override fun print(): String {
|
||||||
return nodes.joinToString("", prefix = "*", postfix = "*") { it.print() }
|
return nodes.joinToString("", prefix = "*", postfix = "*") { it.print() }
|
||||||
|
@ -118,5 +147,11 @@ sealed class AstNode {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data object BreakNode : InlineNode() {
|
||||||
|
override fun print(): String {
|
||||||
|
return "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ class Lexer {
|
||||||
} else if (htmlNest != 0) {
|
} else if (htmlNest != 0) {
|
||||||
codeBuffer.append(" ")
|
codeBuffer.append(" ")
|
||||||
} else {
|
} else {
|
||||||
addBreak(tokens)
|
addBreak(tokens, inQuote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inQuote = false
|
inQuote = false
|
||||||
|
@ -107,6 +107,9 @@ class Lexer {
|
||||||
tokens.removeLast()
|
tokens.removeLast()
|
||||||
tokens.add(LineBreak(1))
|
tokens.add(LineBreak(1))
|
||||||
}
|
}
|
||||||
|
if (lastToken is InQuoteBreak) {
|
||||||
|
tokens.removeLast()
|
||||||
|
}
|
||||||
return tokens
|
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()
|
val lastOrNull = tokens.lastOrNull()
|
||||||
if (lastOrNull is LineBreak && 1 <= lastOrNull.count) {
|
if (lastOrNull is LineBreak && 1 <= lastOrNull.count) {
|
||||||
tokens.removeLast()
|
tokens.removeLast()
|
||||||
|
|
|
@ -28,8 +28,9 @@ class Parser {
|
||||||
is Header -> header(next, iterator)
|
is Header -> header(next, iterator)
|
||||||
is Html -> TODO()
|
is Html -> TODO()
|
||||||
is Token.List -> TODO()
|
is Token.List -> TODO()
|
||||||
is Quote -> TODO()
|
is Quote -> quote(next, iterator)
|
||||||
is Separator -> separator(next, iterator)
|
is Separator -> separator(next, iterator)
|
||||||
|
InQuoteBreak -> TODO()
|
||||||
}
|
}
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
nodes.add(node)
|
nodes.add(node)
|
||||||
|
@ -38,6 +39,44 @@ class Parser {
|
||||||
return RootNode(BodyNode(nodes))
|
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 {
|
fun separator(separator: Separator, iterator: PeekableTokenIterator): AstNode {
|
||||||
return SeparatorNode
|
return SeparatorNode
|
||||||
}
|
}
|
||||||
|
@ -74,7 +113,7 @@ class Parser {
|
||||||
return when (token) {
|
return when (token) {
|
||||||
is Asterisk, is InlineCodeBlock, is Strike,
|
is Asterisk, is InlineCodeBlock, is Strike,
|
||||||
is Text, is Whitespace, Exclamation, ParenthesesEnd, ParenthesesStart,
|
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
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -97,7 +136,10 @@ class Parser {
|
||||||
is UrlTitle -> PlainText("\"${token.title}\"")
|
is UrlTitle -> PlainText("\"${token.title}\"")
|
||||||
is Whitespace -> whitespace(token, iterator)
|
is Whitespace -> whitespace(token, iterator)
|
||||||
is LineBreak -> null
|
is LineBreak -> null
|
||||||
else -> TODO()
|
else -> {
|
||||||
|
println("error" + token)
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
|
@ -271,4 +313,20 @@ class Parser {
|
||||||
iterator.print()
|
iterator.print()
|
||||||
return null
|
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 Text(var text: String) : Token()
|
||||||
data class LineBreak(var count: Int) : Token()
|
data class LineBreak(var count: Int) : Token()
|
||||||
data object BlockBreak : Token()
|
data object BlockBreak : Token()
|
||||||
|
data object InQuoteBreak : Token()
|
||||||
data class Header(var count: Int) : Token()
|
data class Header(var count: Int) : Token()
|
||||||
data class Quote(var count: Int) : Token()
|
data class Quote(var count: Int) : Token()
|
||||||
data class Separator(var count: Int, val char: Char) : Token()
|
data class Separator(var count: Int, val char: Char) : Token()
|
||||||
|
|
|
@ -219,6 +219,30 @@ class LexerTest {
|
||||||
assertContentEquals(listOf(Quote(2), Text(">abcd")), actual)
|
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
|
@Test
|
||||||
fun セパレーター() {
|
fun セパレーター() {
|
||||||
val lexer = Lexer()
|
val lexer = Lexer()
|
||||||
|
|
|
@ -343,4 +343,162 @@ class ParserTest {
|
||||||
), actual
|
), 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