summaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-03-24 14:35:37 +0100
committerLinnea Gräf <nea@nea.moe>2024-03-24 14:35:37 +0100
commitc57016dc5a1f502b637c2f77bcf724165387ef83 (patch)
tree40f82e94e0f3229d081860d893a0433244bcba4c /src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt
downloadblog-infra-c57016dc5a1f502b637c2f77bcf724165387ef83.tar.gz
blog-infra-c57016dc5a1f502b637c2f77bcf724165387ef83.tar.bz2
blog-infra-c57016dc5a1f502b637c2f77bcf724165387ef83.zip
Add markdown parser
Diffstat (limited to 'src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt')
-rw-r--r--src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt96
1 files changed, 96 insertions, 0 deletions
diff --git a/src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt b/src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt
new file mode 100644
index 0000000..86b92d2
--- /dev/null
+++ b/src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt
@@ -0,0 +1,96 @@
+package moe.nea.blog.md
+
+import moe.nea.blog.util.indentSize
+import java.util.*
+
+
+class MarkdownParser(source: String) {
+ private val lines = source.lines()
+ private var lineIndex = 0
+ private var blockIndents = 0
+ private val indentStack = Stack<Int>()
+
+ private val blockParsers = mutableListOf<BlockParser>()
+ private val inlineParsers = mutableListOf<InlineParser>()
+
+ fun findParserFor(line: String): BlockParser? {
+ return blockParsers.filter { it.detect(line) }
+ .maxByOrNull { it.prio }
+ }
+
+ fun readChildBlock(): MarkdownBlock? {
+ val peek = peekLine() ?: return null
+ val blockParser = findParserFor(peek) ?: ParagraphParser
+ return blockParser.parse(this)
+ }
+
+ fun pushIndent(newIndent: Int) {
+ require(newIndent > blockIndents)
+ indentStack.push(blockIndents)
+ blockIndents = newIndent
+ }
+
+ fun popIndent() {
+ blockIndents = indentStack.pop()
+ }
+
+ fun consumeLine(): String? {
+ val line = peekLine()
+ if (line != null)
+ lineIndex++
+ return line
+ }
+
+ fun peekLine(): String? {
+ if (lineIndex !in lines.indices) return null
+ val line = lines[lineIndex]
+ val indent = line.indentSize()
+ if (indent != null && indent < blockIndents)
+ return null
+ return line.substring(blockIndents)
+ }
+
+ fun parseInlineTextOnce(lookback: MarkdownFormat, text: String): Pair<MarkdownFormat, String> {
+ val parser = inlineParsers.find { it.detect(lookback, text) }
+ if (parser != null)
+ return parser.parse(this, text)
+ require(!text.isEmpty()) // TODO handle empty string
+ if (text[0] == ' ')
+ return Pair(Whitespace(), text.substring(1))
+ val nextSpecial = text.indexOfFirst { it in inlineParsers.flatMap { it.specialSyntax } || it == ' ' }
+ return Pair(Word(text.substring(0, nextSpecial)), text.substring(nextSpecial))
+ }
+
+ fun parseInlineText(text: String): MarkdownFormat {
+ val seq = mutableListOf<MarkdownFormat>()
+ var remaining = text
+ var lastToken: MarkdownFormat = Begin()
+ while (remaining.isNotEmpty()) {
+ val (tok, next) = parseInlineTextOnce(lastToken, remaining)
+ seq.add(tok)
+ lastToken = tok
+ remaining = next
+ }
+ return collapseInlineFormat(seq)
+ }
+
+ fun collapseInlineFormat(sequence: List<MarkdownFormat>): MarkdownFormat {
+ return FormatSequence(sequence)
+ }
+
+ fun readDocument(): Document {
+ val list = mutableListOf<MarkdownBlock>()
+ while (true) {
+ val block = readChildBlock() ?: break
+ list.add(block)
+ }
+ return Document(list)
+ }
+
+ fun addDefaultParsers() {
+ blockParsers.add(CodeBlockParser)
+ blockParsers.add(HeaderParser)
+ inlineParsers.add(ItalicsParser)
+ }
+}
+