feat: 一部のパースができるように
This commit is contained in:
parent
daed5fb235
commit
815425f01e
|
@ -4,6 +4,8 @@
|
|||
|
||||
## What is it?
|
||||
|
||||
***abc**d**e**f*
|
||||
|
||||
This repository contains a simple library project, intended to demonstrate
|
||||
a [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library that is deployable
|
||||
to [Maven Central](https://central.sonatype.com/).
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
package io.github.kotlin.fibonacci
|
||||
|
||||
actual val firstElement: Int = 1
|
||||
actual val secondElement: Int = 2
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -2,18 +2,70 @@ package dev.usbharu.markdown
|
|||
|
||||
import kotlin.collections.List
|
||||
|
||||
sealed class AstNode
|
||||
data class RootNode(val node: AstNode) : AstNode()
|
||||
data class BodyNode(val body: List<AstNode>) : AstNode()
|
||||
sealed class AstNode {
|
||||
open fun print(): String {
|
||||
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()
|
||||
|
||||
data class HeaderNode(val header: Int, val headerText: HeaderText?) : BlockNode()
|
||||
data class HeaderText(val text: String) : BlockNode()
|
||||
data class HeaderNode(val header: Int, val headerTextNode: HeaderTextNode?) : 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
|
||||
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()
|
||||
data class DiscListNode(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
|
||||
}
|
||||
}
|
|
@ -3,7 +3,172 @@ package dev.usbharu.markdown
|
|||
import kotlin.collections.List
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -35,4 +35,28 @@ class PeekableStringIterator(private val list: List<String>) : Iterator<String>
|
|||
fun peekOrNull(): String? = list.getOrNull(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")
|
||||
|
||||
}
|
||||
}
|
|
@ -128,6 +128,41 @@ class LexerTest {
|
|||
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
|
||||
fun 引用() {
|
||||
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
|
||||
fun アンダーバー() {
|
||||
val lexer = Lexer()
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package io.github.kotlin.fibonacci
|
||||
|
||||
actual val firstElement: Int = 3
|
||||
actual val secondElement: Int = 5
|
|
@ -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())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue