feat: 段落をちゃんと処理できるように
This commit is contained in:
parent
579343145e
commit
8d2e2246cb
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,21 +86,33 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node != null) {
|
||||||
return mutableListOf(node)
|
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 {
|
||||||
return PlainText(token.whitespace.toString().repeat(token.count))
|
return PlainText(token.whitespace.toString().repeat(token.count))
|
||||||
|
@ -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)!!)
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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('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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue