feat: 段落をちゃんと処理できるように

This commit is contained in:
usbharu 2024-11-16 23:04:12 +09:00
parent 579343145e
commit 8d2e2246cb
Signed by: usbharu
GPG Key ID: 95CBCF7046307B77
7 changed files with 128 additions and 34 deletions

View File

@ -106,5 +106,17 @@ sealed class AstNode {
return name return name
} }
} }
data class InlineCodeNode(val code: String) : InlineNode() {
override fun print(): String {
return "`$code`"
}
}
data class SimpleUrlNode(val url: String) : InlineNode() {
override fun print(): String {
return url
}
}
} }

View File

@ -89,20 +89,24 @@ class Lexer {
} else if (htmlNest != 0) { } else if (htmlNest != 0) {
codeBuffer.append(" ") codeBuffer.append(" ")
} else { } else {
tokens.add(Break(1)) addBreak(tokens)
} }
} }
inQuote = false inQuote = false
} }
val lastToken = tokens.lastOrNull() val lastToken = tokens.lastOrNull()
if (lastToken is Break) { if (lastToken is LineBreak) {
if (lastToken.count == 1) { if (lastToken.count == 1) {
tokens.removeLast() tokens.removeLast()
} else { } else {
lastToken.count-- lastToken.count--
} }
} }
if (lastToken is BlockBreak) {
tokens.removeLast()
tokens.add(LineBreak(1))
}
return tokens return tokens
} }
@ -517,15 +521,19 @@ class Lexer {
lines: PeekableStringIterator, lines: PeekableStringIterator,
tokens: MutableList<Token>, tokens: MutableList<Token>,
) { ) {
var count = 0
while (lines.peekOrNull() == "") { while (lines.peekOrNull() == "") {
lines.next() lines.skip()
count++ addBreak(tokens)
} }
if (tokens.lastOrNull() is Break) { }
tokens[tokens.lastIndex] = Break(count + 1)
fun addBreak(tokens: MutableList<Token>) {
val lastOrNull = tokens.lastOrNull()
if (lastOrNull is LineBreak && 1 <= lastOrNull.count) {
tokens.removeLast()
tokens.add(BlockBreak)
} else { } else {
tokens.add(Break(count)) tokens.add(LineBreak(1))
} }
} }
} }

View File

@ -16,12 +16,12 @@ class Parser {
val node = when (val next = iterator.next()) { val node = when (val next = iterator.next()) {
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 -> paragraph( SquareBracketStart, SquareBracketEnd, is Url, is UrlTitle, is LineBreak -> paragraph(
next, next,
iterator iterator
) )
is Break -> null //todo ただの改行と段落分けの改行のトークンを分ける is BlockBreak -> null
is CheckBox -> TODO() is CheckBox -> TODO()
is CodeBlock -> TODO() is CodeBlock -> TODO()
is CodeBlockLanguage -> TODO() is CodeBlockLanguage -> TODO()
@ -53,8 +53,31 @@ class Parser {
return HeaderNode(header.count, headerTextNode) return HeaderNode(header.count, headerTextNode)
} }
fun paragraph(token: Token, iterator: PeekableTokenIterator): AstNode { fun paragraph(token: Token, iterator: PeekableTokenIterator): AstNode? {
return ParagraphNode(inline(token, iterator)) val list = mutableListOf<InlineNode>()
var token2: Token? = token
do {
list.addAll(inline(token2!!, iterator))
if (iterator.hasNext() && isInline(iterator.peekOrNull())) {
token2 = iterator.next()
} else {
token2 = iterator.peekOrNull()
}
} while (iterator.hasNext() && isInline(token2))
if (list.isEmpty()) {
return null
}
return ParagraphNode(list)
}
fun isInline(token: Token?): Boolean {
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
else -> false
}
} }
fun inline(token: Token, iterator: PeekableTokenIterator): MutableList<InlineNode> { fun inline(token: Token, iterator: PeekableTokenIterator): MutableList<InlineNode> {
@ -63,20 +86,32 @@ class Parser {
val node = when (token) { val node = when (token) {
is Asterisk -> asterisk(token, iterator) is Asterisk -> asterisk(token, iterator)
Exclamation -> image(Exclamation, iterator) Exclamation -> image(Exclamation, iterator)
is InlineCodeBlock -> TODO() is InlineCodeBlock -> inlineCodeBlock(token, iterator)
ParenthesesEnd -> PlainText(")") ParenthesesEnd -> PlainText(")")
ParenthesesStart -> PlainText("(") ParenthesesStart -> PlainText("(")
SquareBracketEnd -> PlainText("]") SquareBracketEnd -> PlainText("]")
SquareBracketStart -> url(SquareBracketStart, iterator) SquareBracketStart -> url(SquareBracketStart, iterator)
is Strike -> TODO() is Strike -> TODO()
is Text -> plainText(token, iterator) is Text -> plainText(token, iterator)
is Url -> TODO() is Url -> inlineUrl(token, iterator)
is UrlTitle -> PlainText("\"${token.title}\"") is UrlTitle -> PlainText("\"${token.title}\"")
is Whitespace -> whitespace(token, iterator) is Whitespace -> whitespace(token, iterator)
is LineBreak -> null
else -> TODO() else -> TODO()
} }
return mutableListOf(node) if (node != null) {
return mutableListOf(node)
}
return mutableListOf()
}
fun inlineUrl(url: Url, iterator: PeekableTokenIterator): SimpleUrlNode {
return SimpleUrlNode(url.url)
}
fun inlineCodeBlock(inlineCodeBlock: InlineCodeBlock, iterator: PeekableTokenIterator): InlineCodeNode {
return InlineCodeNode(inlineCodeBlock.text)
} }
fun whitespace(token: Whitespace, iterator: PeekableTokenIterator): InlineNode { fun whitespace(token: Whitespace, iterator: PeekableTokenIterator): InlineNode {
@ -207,7 +242,7 @@ class Parser {
var counter = 0 var counter = 0
val tokens = mutableListOf<Token>() val tokens = mutableListOf<Token>()
while (iterator.peekOrNull(counter) != null && while (iterator.peekOrNull(counter) != null &&
iterator.peekOrNull(counter) !is Break && iterator.peekOrNull(counter) !is LineBreak &&
iterator.peekOrNull(counter) !is Asterisk iterator.peekOrNull(counter) !is Asterisk
) { ) {
tokens.add(iterator.peekOrNull(counter)!!) tokens.add(iterator.peekOrNull(counter)!!)

View File

@ -33,6 +33,10 @@ class PeekableStringIterator(private val list: List<String>) : Iterator<String>
fun peekOrNull(): String? = list.getOrNull(index) fun peekOrNull(): String? = list.getOrNull(index)
fun current(): Int = index fun current(): Int = index
fun skip(count: Int = 1) {
index += count
}
} }
class PeekableTokenIterator(private val tokens: List<Token>) : Iterator<Token> { class PeekableTokenIterator(private val tokens: List<Token>) : Iterator<Token> {

View File

@ -5,7 +5,8 @@ import kotlin.js.JsExport
@JsExport @JsExport
sealed class Token { sealed class Token {
data class Text(var text: String) : Token() data class Text(var text: String) : Token()
data class Break(var count: Int) : Token() data class LineBreak(var count: Int) : Token()
data object BlockBreak : 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()

View File

@ -15,7 +15,7 @@ class LexerTest {
println(actual) println(actual)
assertContentEquals(listOf(Break(1)), actual) assertContentEquals(listOf(LineBreak(1)), actual)
} }
@Test @Test
@ -26,7 +26,7 @@ class LexerTest {
println(actual) println(actual)
assertContentEquals(listOf(Break(1)), actual) assertContentEquals(listOf(LineBreak(1)), actual)
} }
@Test @Test
@ -37,7 +37,7 @@ class LexerTest {
println(actual) println(actual)
assertContentEquals(listOf(Break(2)), actual) assertContentEquals(listOf(BlockBreak), actual)
} }
@Test @Test
@ -59,7 +59,7 @@ class LexerTest {
println(actual) println(actual)
assertContentEquals(listOf(Text("abcd"), Break(1), Text("efgh")), actual) assertContentEquals(listOf(Text("abcd"), LineBreak(1), Text("efgh")), actual)
} }
@Test @Test
@ -70,7 +70,7 @@ class LexerTest {
println(actual) println(actual)
assertContentEquals(listOf(Text("abcd"), Break(2), Text("efgh")), actual) assertContentEquals(listOf(Text("abcd"), BlockBreak, Text("efgh")), actual)
} }
@Test @Test
@ -141,7 +141,7 @@ class LexerTest {
listOf( listOf(
Header(1), Header(1),
Text("a"), Text("a"),
Break(1), LineBreak(1),
Header(1), Header(1),
Text("b") Text("b")
), actual ), actual
@ -331,15 +331,15 @@ class LexerTest {
DiscList, DiscList,
CheckBox(false), CheckBox(false),
Text("a"), Text("a"),
Break(1), LineBreak(1),
DiscList, DiscList,
CheckBox(true), CheckBox(true),
Text("b"), Text("b"),
Break(1), LineBreak(1),
DiscList, DiscList,
CheckBox(false), CheckBox(false),
Text("c"), Text("c"),
Break(1), LineBreak(1),
DiscList, DiscList,
CheckBox(true), CheckBox(true),
Text("d"), Text("d"),
@ -366,7 +366,7 @@ class LexerTest {
println(actual) println(actual)
assertContentEquals(listOf(DiscList, Text("aiueo"), Break(1), DiscList, Text("abcd")), actual) assertContentEquals(listOf(DiscList, Text("aiueo"), LineBreak(1), DiscList, Text("abcd")), actual)
} }
@Test @Test
@ -378,7 +378,7 @@ class LexerTest {
println(actual) println(actual)
assertContentEquals( assertContentEquals(
listOf(DiscList, Text("aiueo"), Break(1), Whitespace(4, ' '), DiscList, Text("abcd")), actual listOf(DiscList, Text("aiueo"), LineBreak(1), Whitespace(4, ' '), DiscList, Text("abcd")), actual
) )
} }
@ -392,7 +392,7 @@ class LexerTest {
assertContentEquals( assertContentEquals(
listOf( listOf(
DecimalList('1'), Text("aiueo"), Break(1), Whitespace(4, ' '), DecimalList('2'), Text("abcd") DecimalList('1'), Text("aiueo"), LineBreak(1), Whitespace(4, ' '), DecimalList('2'), Text("abcd")
), actual ), actual
) )
} }
@ -407,7 +407,7 @@ class LexerTest {
assertContentEquals( assertContentEquals(
listOf( listOf(
DecimalList(''), Text("aiueo"), Break(1), Whitespace(4, ' '), DecimalList(''), Text("abcd") DecimalList(''), Text("aiueo"), LineBreak(1), Whitespace(4, ' '), DecimalList(''), Text("abcd")
), actual ), actual
) )
} }
@ -422,7 +422,7 @@ class LexerTest {
assertContentEquals( assertContentEquals(
listOf( listOf(
DecimalList('1'), Text("aiueo"), Break(1), Whitespace(4, ' '), DecimalList('2'), Text("abcd") DecimalList('1'), Text("aiueo"), LineBreak(1), Whitespace(4, ' '), DecimalList('2'), Text("abcd")
), actual ), actual
) )
} }
@ -463,7 +463,7 @@ class LexerTest {
assertContentEquals( assertContentEquals(
listOf( listOf(
Text("こんにちは~"), Whitespace(1, ' '), Url("https://example.com"), Break(1), Text("あいうえお") Text("こんにちは~"), Whitespace(1, ' '), Url("https://example.com"), LineBreak(1), Text("あいうえお")
), actual ), actual
) )
} }

View File

@ -30,7 +30,7 @@ class ParserTest {
fun header複数() { fun header複数() {
val parser = Parser() val parser = Parser()
val actual = parser.parse(listOf(Header(1), Text("a b c"), Break(1), Header(2), Text("d e f"))) val actual = parser.parse(listOf(Header(1), Text("a b c"), LineBreak(1), Header(2), Text("d e f")))
println(actual) println(actual)
println(actual.print()) println(actual.print())
@ -235,7 +235,7 @@ class ParserTest {
fun separator2() { fun separator2() {
val parser = Parser() val parser = Parser()
val actual = parser.parse(listOf(Separator(3, '-'), Break(1), Separator(3, '-'))) val actual = parser.parse(listOf(Separator(3, '-'), LineBreak(1), Separator(3, '-')))
println(actual) println(actual)
println(actual.print()) println(actual.print())
@ -309,4 +309,38 @@ class ParserTest {
), actual ), actual
) )
} }
@Test
fun 複数段落() {
val parser = Parser()
val actual = parser.parse(
listOf(
Text("aiueo"), LineBreak(1), Text("abcd"), BlockBreak, Text("hoge")
)
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
ParagraphNode(
listOf(
PlainText("aiueo"),
PlainText(("abcd"))
)
),
ParagraphNode(
listOf(
PlainText("hoge")
)
)
)
)
), actual
)
}
} }