1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
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, true)
}
private fun expandMarkdownFormats(sequence: List<MarkdownFormat>): List<MarkdownFormat> {
val elongated = mutableListOf<MarkdownFormat>()
for (markdownFormat in sequence) {
if (markdownFormat is FormatSequence) {
elongated.addAll(expandMarkdownFormats(markdownFormat.list))
} else {
elongated.add(markdownFormat)
}
}
return elongated
}
private fun collapseMarkdownFormats(
sequence: List<MarkdownFormat>,
trimWhitespace: Boolean
): MutableList<MarkdownFormat> {
val shortened = mutableListOf<MarkdownFormat>()
var last: MarkdownFormat = if (trimWhitespace) Whitespace() else Begin()
for (format in sequence) {
if (format is Whitespace && last is Whitespace) {
continue
}
last = format
shortened.add(format)
}
return shortened
}
fun collapseInlineFormat(sequence: List<MarkdownFormat>, trimWhitespace: Boolean): MarkdownFormat {
return FormatSequence(collapseMarkdownFormats(expandMarkdownFormats(sequence), trimWhitespace))
}
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)
}
}
|