feat: 一部のパースができるように

This commit is contained in:
usbharu 2024-11-15 13:42:13 +09:00
parent daed5fb235
commit 815425f01e
Signed by: usbharu
GPG Key ID: 95CBCF7046307B77
10 changed files with 557 additions and 40 deletions

View File

@ -4,6 +4,8 @@
## What is it? ## What is it?
***abc**d**e**f*
This repository contains a simple library project, intended to demonstrate This repository contains a simple library project, intended to demonstrate
a [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library that is deployable a [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library that is deployable
to [Maven Central](https://central.sonatype.com/). to [Maven Central](https://central.sonatype.com/).

View File

@ -1,4 +0,0 @@
package io.github.kotlin.fibonacci
actual val firstElement: Int = 1
actual val secondElement: Int = 2

View File

@ -1,12 +0,0 @@
package io.github.kotlin.fibonacci
import kotlin.test.Test
import kotlin.test.assertEquals
class AndroidFibiTest {
@Test
fun `test 3rd element`() {
assertEquals(3, generateFibi().take(3).last())
}
}

View File

@ -2,18 +2,70 @@ package dev.usbharu.markdown
import kotlin.collections.List import kotlin.collections.List
sealed class AstNode sealed class AstNode {
data class RootNode(val node: AstNode) : AstNode() open fun print(): String {
data class BodyNode(val body: List<AstNode>) : AstNode() return toString()
}
}
data class RootNode(val node: AstNode) : AstNode() {
override fun print(): String {
return node.print()
}
}
data class BodyNode(val body: List<AstNode>) : AstNode() {
override fun print(): String {
return body.joinToString("\n") { it.print() }
}
}
sealed class BlockNode : AstNode() sealed class BlockNode : AstNode()
data class HeaderNode(val header: Int, val headerText: HeaderText?) : BlockNode() data class HeaderNode(val header: Int, val headerTextNode: HeaderTextNode?) : BlockNode() {
data class HeaderText(val text: String) : BlockNode() override fun print(): String {
return "#".repeat(header) + " " + headerTextNode?.print().orEmpty()
}
}
data class HeaderTextNode(val text: String) : BlockNode() {
override fun print(): String {
return text
}
}
sealed interface QuotableNode sealed interface QuotableNode
data class QuoteNode(val nodes: List<QuotableNode>) : AstNode(), QuotableNode data class QuoteNode(val nodes: List<QuotableNode>) : AstNode(), QuotableNode
data object SeparatorNode : BlockNode() data object SeparatorNode : BlockNode() {
override fun print(): String {
return "---"
}
}
sealed class ListNode : BlockNode() sealed class ListNode : BlockNode()
data class DiscListNode(val node: InlineNode, val childList: List<ListNode>) : ListNode() data class DiscListNode(val node: InlineNode, val childList: List<ListNode>) : ListNode()
data class DecimalListNode(val node: InlineNode, val childList: List<ListNode>) : ListNode() data class DecimalListNode(val node: InlineNode, val childList: List<ListNode>) : ListNode()
data class ParagraphNode(val nodes: List<InlineNode>) : ListNode() {
override fun print(): String {
return nodes.joinToString("\n") { it.print() }
}
}
sealed class InlineNode : AstNode(), QuotableNode sealed class InlineNode : AstNode(), QuotableNode
data class ItalicNode(val nodes: MutableList<InlineNode>) : InlineNode() {
override fun print(): String {
return nodes.joinToString("", prefix = "*", postfix = "*") { it.print() }
}
}
data class BoldNode(val nodes: MutableList<InlineNode>) : InlineNode() {
override fun print(): String {
return nodes.joinToString("", prefix = "**", postfix = "**") { it.print() }
}
}
data class StrikeThroughNode(val nodes: List<InlineNode>) : InlineNode()
data class PlainText(val text: String) : InlineNode() {
override fun print(): String {
return text
}
}

View File

@ -3,7 +3,172 @@ package dev.usbharu.markdown
import kotlin.collections.List import kotlin.collections.List
class Parser { class Parser {
fun parse(tokens: List<Token>) { fun parse(tokens: List<Token>): AstNode {
val iterator = PeekableTokenIterator(tokens)
val nodes = mutableListOf<AstNode>()
while (iterator.hasNext()) {
val node = when (val next = iterator.next()) {
is Asterisk -> paragraph(next, iterator)
is Break -> null
is CheckBox -> TODO()
is CodeBlock -> TODO()
is CodeBlockLanguage -> TODO()
Exclamation -> TODO()
is Header -> header(next, iterator)
is Html -> TODO()
is InlineCodeBlock -> TODO()
is dev.usbharu.markdown.List -> TODO()
ParenthesesEnd -> TODO()
ParenthesesStart -> TODO()
is Quote -> TODO()
is Separator -> separator(next, iterator)
SquareBracketEnd -> TODO()
SquareBracketStart -> TODO()
is Strike -> TODO()
is Text -> paragraph(next, iterator)
is Url -> TODO()
is UrlTitle -> TODO()
is Whitespace -> TODO()
}
if (node != null) {
nodes.add(node)
}
}
return RootNode(BodyNode(nodes))
}
fun separator(separator: Separator, iterator: PeekableTokenIterator): AstNode {
return SeparatorNode
}
fun header(header: Header, iterator: PeekableTokenIterator): AstNode {
val peekOrNull = iterator.peekOrNull()
val headerTextNode = if (peekOrNull is Text) {
iterator.next()
HeaderTextNode(peekOrNull.text)
} else {
null
}
return HeaderNode(header.count, headerTextNode)
}
fun paragraph(token: Token, iterator: PeekableTokenIterator): AstNode {
return ParagraphNode(inline(token, iterator))
}
fun inline(token: Token, iterator: PeekableTokenIterator): MutableList<InlineNode> {
println("inline start token:$token")
iterator.print()
val node = when (token) {
is Asterisk -> asterisk(token, iterator)
Exclamation -> TODO()
is InlineCodeBlock -> TODO()
ParenthesesEnd -> TODO()
ParenthesesStart -> TODO()
SquareBracketEnd -> TODO()
SquareBracketStart -> TODO()
is Strike -> TODO()
is Text -> plainText(token, iterator)
is Url -> TODO()
is UrlTitle -> TODO()
is Whitespace -> TODO()
else -> TODO()
}
return mutableListOf(node)
}
fun plainText(token: Text, iterator: PeekableTokenIterator): PlainText {
return PlainText(token.text)
}
fun asterisk(token: Asterisk, iterator: PeekableTokenIterator): InlineNode {
var count = token.count
var node: InlineNode? = null
while ((count > 0)) {
if (count == 3) {
val italicBold = italic(token, iterator, 3)
if (italicBold != null) {
return italicBold
}
count--
}
if (count == 2) {
val italicBold = italic(token, iterator, 2)
if (italicBold != null) {
if (node == null) {
node = italicBold
count = 1
continue
} else {
when (node) {
is BoldNode -> node.nodes.add(italicBold)
is ItalicNode -> node.nodes.add(italicBold)
else -> TODO()
}
return node
}
}
count--
}
if (count == 1) {
val italicBold = italic(token, iterator, 1)
if (italicBold != null) {
if (node == null) {
node = italicBold
count = 2
continue
} else {
when (node) {
is BoldNode -> node.nodes.add(italicBold)
is ItalicNode -> node.nodes.add(italicBold)
else -> TODO()
}
return node
}
}
count--
}
}
return node!!
}
fun italic(token: Asterisk, iterator: PeekableTokenIterator, count: Int): InlineNode? {
println("italic $count")
iterator.print()
var counter = 0
val tokens = mutableListOf<Token>()
while (iterator.peekOrNull(counter) != null &&
iterator.peekOrNull(counter) !is Break &&
iterator.peekOrNull(counter) !is Asterisk
) {
tokens.add(iterator.peekOrNull(counter)!!)
println(tokens)
counter++
}
if (iterator.peekOrNull(counter) != null &&
(iterator.peekOrNull(counter) is Asterisk &&
(iterator.peekOrNull(counter) as Asterisk).count == count)
) {
println("italic found!!! $count")
iterator.skip(counter + 1)
val inline = inline(tokens.first(), PeekableTokenIterator(tokens))
return when (count) {
1 -> ItalicNode(inline)
2 -> BoldNode(inline)
3 -> ItalicNode(mutableListOf(BoldNode(inline)))
else -> {
TODO()
}
}
}
println("return null")
iterator.print()
return null
} }
} }

View File

@ -35,4 +35,28 @@ 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
}
class PeekableTokenIterator(private val tokens: List<Token>) : Iterator<Token> {
private var index = 0
override fun hasNext(): Boolean = index < tokens.size
override fun next(): Token = try {
tokens[index++]
} catch (e: IndexOutOfBoundsException) {
index -= 1; throw NoSuchElementException(e.message)
}
fun peekOrNull(): Token? = tokens.getOrNull(index)
fun peekOrNull(offset: Int): Token? = tokens.getOrNull(index + offset)
fun current(): Int = index
fun skip(count: Int = 0) {
index += count
}
fun print() {
println("token: $tokens\nindex: $index")
}
} }

View File

@ -128,6 +128,41 @@ class LexerTest {
assertContentEquals(listOf(Header(1), Text("#a")), actual) assertContentEquals(listOf(Header(1), Text("#a")), actual)
} }
@Test
fun ヘッダー後の改行() {
val lexer = Lexer()
val actual = lexer.lex("# a\n# b")
println(actual)
assertContentEquals(
listOf(
Header(1),
Text("a"),
Break(1),
Header(1),
Text("b")
), actual
)
}
@Test
fun ヘッダー複数() {
val lexer = Lexer()
val actual = lexer.lex("# a a a")
println(actual)
assertContentEquals(
listOf(
Header(1),
Text("a a a"),
), actual
)
}
@Test @Test
fun 引用() { fun 引用() {
val lexer = Lexer() val lexer = Lexer()
@ -507,6 +542,25 @@ class LexerTest {
) )
} }
@Test
fun アスタリスク複数2() {
val lexer = Lexer()
val actual = lexer.lex("***a**b*")
println(actual)
assertContentEquals(
listOf(
Asterisk(3, '*'),
Text("a"),
Asterisk(2, '*'),
Text("b"),
Asterisk(1, '*'),
), actual
)
}
@Test @Test
fun アンダーバー() { fun アンダーバー() {
val lexer = Lexer() val lexer = Lexer()

View File

@ -0,0 +1,252 @@
package dev.usbharu.markdown
import kotlin.test.Test
import kotlin.test.assertEquals
class ParserTest {
@Test
fun header() {
val parser = Parser()
val actual = parser.parse(listOf(Header(1), Text("a b c")))
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
HeaderNode(1, HeaderTextNode("a b c"))
)
)
), actual
)
}
@Test
fun header複数() {
val parser = Parser()
val actual = parser.parse(listOf(Header(1), Text("a b c"), Break(1), Header(2), Text("d e f")))
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
HeaderNode(1, HeaderTextNode("a b c")),
HeaderNode(2, HeaderTextNode("d e f")),
)
)
), actual
)
}
@Test
fun asterisk() {
val parser = Parser()
val actual = parser.parse(listOf(Asterisk(1, '*'), Text("a"), Asterisk(1, '*')))
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
ParagraphNode(listOf(ItalicNode(mutableListOf(PlainText("a")))))
)
)
), actual
)
}
@Test
fun asterisk2() {
val parser = Parser()
val actual = parser.asterisk(
Asterisk(1, '*'), PeekableTokenIterator(listOf(Text("a"), Asterisk(1, '*')))
)
println(actual)
println(actual.print())
}
@Test
fun bold() {
val parser = Parser()
val actual = parser.parse(listOf(Asterisk(2, '*'), Text("a"), Asterisk(2, '*')))
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
ParagraphNode(listOf(BoldNode(mutableListOf(PlainText("a")))))
)
)
), actual
)
}
@Test
fun italicBold() {
val parser = Parser()
val actual = parser.parse(listOf(Asterisk(3, '*'), Text("a"), Asterisk(3, '*')))
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
ParagraphNode(listOf(ItalicNode(mutableListOf(BoldNode(mutableListOf(PlainText("a")))))))
)
)
), actual
)
}
@Test
fun italicとbold() {
val parser = Parser()
val actual = parser.parse(
listOf(
Asterisk(3, '*'), Text("a"), Asterisk(2, '*'), Text("b"), Asterisk(1, '*')
)
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
ParagraphNode(
listOf(
BoldNode(
mutableListOf(
PlainText("a"), ItalicNode(mutableListOf(PlainText("b")))
)
)
)
)
)
)
), actual
)
}
@Test
fun italicとbold2() {
val parser = Parser()
val actual = parser.parse(
listOf(
Asterisk(3, '*'), Text("a"), Asterisk(1, '*'), Text("b"), Asterisk(2, '*')
)
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
ParagraphNode(
listOf(
ItalicNode(
mutableListOf(
PlainText("a"), BoldNode(mutableListOf(PlainText("b")))
)
)
)
)
)
)
), actual
)
}
@Test
fun plainText() {
val parser = Parser()
val actual = parser.parse(
listOf(
Text("hello")
)
)
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
ParagraphNode(
listOf(
PlainText("hello")
)
)
)
)
), actual
)
}
@Test
fun separator() {
val parser = Parser()
val actual = parser.parse(listOf(Separator(3, '-')))
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
SeparatorNode
)
)
), actual
)
}
@Test
fun separator2() {
val parser = Parser()
val actual = parser.parse(listOf(Separator(3, '-'), Break(1), Separator(3, '-')))
println(actual)
println(actual.print())
assertEquals(
RootNode(
BodyNode(
listOf(
SeparatorNode,
SeparatorNode,
)
)
), actual
)
}
}

View File

@ -1,4 +0,0 @@
package io.github.kotlin.fibonacci
actual val firstElement: Int = 3
actual val secondElement: Int = 5

View File

@ -1,12 +0,0 @@
package io.github.kotlin.fibonacci
import kotlin.test.Test
import kotlin.test.assertEquals
class LinuxFibiTest {
@Test
fun `test 3rd element`() {
assertEquals(8, generateFibi().take(3).last())
}
}