diff --git a/README.md b/README.md index daaa4dc..e8b07bc 100644 --- a/README.md +++ b/README.md @@ -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/). diff --git a/library/src/androidMain/kotlin/fibiprops.android.kt b/library/src/androidMain/kotlin/fibiprops.android.kt deleted file mode 100644 index ed4c096..0000000 --- a/library/src/androidMain/kotlin/fibiprops.android.kt +++ /dev/null @@ -1,4 +0,0 @@ -package io.github.kotlin.fibonacci - -actual val firstElement: Int = 1 -actual val secondElement: Int = 2 \ No newline at end of file diff --git a/library/src/androidUnitTest/kotlin/AndroidFibiTest.kt b/library/src/androidUnitTest/kotlin/AndroidFibiTest.kt deleted file mode 100644 index 64199be..0000000 --- a/library/src/androidUnitTest/kotlin/AndroidFibiTest.kt +++ /dev/null @@ -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()) - } -} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/dev/usbharu/markdown/AstNode.kt b/library/src/commonMain/kotlin/dev/usbharu/markdown/AstNode.kt index 068b181..f1e8785 100644 --- a/library/src/commonMain/kotlin/dev/usbharu/markdown/AstNode.kt +++ b/library/src/commonMain/kotlin/dev/usbharu/markdown/AstNode.kt @@ -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() +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() { + 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) : 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() data class DecimalListNode(val node: InlineNode, val childList: List) : ListNode() +data class ParagraphNode(val nodes: List) : ListNode() { + override fun print(): String { + return nodes.joinToString("\n") { it.print() } + } +} -sealed class InlineNode : AstNode(), QuotableNode \ No newline at end of file +sealed class InlineNode : AstNode(), QuotableNode +data class ItalicNode(val nodes: MutableList) : InlineNode() { + override fun print(): String { + return nodes.joinToString("", prefix = "*", postfix = "*") { it.print() } + } +} + +data class BoldNode(val nodes: MutableList) : InlineNode() { + override fun print(): String { + return nodes.joinToString("", prefix = "**", postfix = "**") { it.print() } + } +} + +data class StrikeThroughNode(val nodes: List) : InlineNode() +data class PlainText(val text: String) : InlineNode() { + override fun print(): String { + return text + } +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/dev/usbharu/markdown/Parser.kt b/library/src/commonMain/kotlin/dev/usbharu/markdown/Parser.kt index be319c7..cf47c8d 100644 --- a/library/src/commonMain/kotlin/dev/usbharu/markdown/Parser.kt +++ b/library/src/commonMain/kotlin/dev/usbharu/markdown/Parser.kt @@ -3,7 +3,172 @@ package dev.usbharu.markdown import kotlin.collections.List class Parser { - fun parse(tokens: List) { + fun parse(tokens: List): AstNode { + val iterator = PeekableTokenIterator(tokens) + + val nodes = mutableListOf() + 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 { + 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() + 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 } } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/dev/usbharu/markdown/PeekableIterator.kt b/library/src/commonMain/kotlin/dev/usbharu/markdown/PeekableIterator.kt index 42634e7..b40bea4 100644 --- a/library/src/commonMain/kotlin/dev/usbharu/markdown/PeekableIterator.kt +++ b/library/src/commonMain/kotlin/dev/usbharu/markdown/PeekableIterator.kt @@ -35,4 +35,28 @@ class PeekableStringIterator(private val list: List) : Iterator fun peekOrNull(): String? = list.getOrNull(index) fun current(): Int = index +} + +class PeekableTokenIterator(private val tokens: List) : Iterator { + 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") + + } } \ No newline at end of file diff --git a/library/src/commonTest/kotlin/dev/usbharu/markdown/LexerTest.kt b/library/src/commonTest/kotlin/dev/usbharu/markdown/LexerTest.kt index 8f12903..9bdb81d 100644 --- a/library/src/commonTest/kotlin/dev/usbharu/markdown/LexerTest.kt +++ b/library/src/commonTest/kotlin/dev/usbharu/markdown/LexerTest.kt @@ -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() diff --git a/library/src/commonTest/kotlin/dev/usbharu/markdown/ParserTest.kt b/library/src/commonTest/kotlin/dev/usbharu/markdown/ParserTest.kt new file mode 100644 index 0000000..1d8ad7d --- /dev/null +++ b/library/src/commonTest/kotlin/dev/usbharu/markdown/ParserTest.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/library/src/linuxX64Main/kotlin/fibiprops.linuxX64.kt b/library/src/linuxX64Main/kotlin/fibiprops.linuxX64.kt deleted file mode 100644 index 7c2aefd..0000000 --- a/library/src/linuxX64Main/kotlin/fibiprops.linuxX64.kt +++ /dev/null @@ -1,4 +0,0 @@ -package io.github.kotlin.fibonacci - -actual val firstElement: Int = 3 -actual val secondElement: Int = 5 \ No newline at end of file diff --git a/library/src/linuxX64Test/kotlin/LinuxFibiTest.kt b/library/src/linuxX64Test/kotlin/LinuxFibiTest.kt deleted file mode 100644 index ffd280d..0000000 --- a/library/src/linuxX64Test/kotlin/LinuxFibiTest.kt +++ /dev/null @@ -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()) - } -}