feat: リストとセパレーターの字句解析を追加
This commit is contained in:
parent
fae06cbb86
commit
9a9de9e062
|
@ -1,5 +1,7 @@
|
|||
package dev.usbharu.markdown
|
||||
|
||||
import kotlin.collections.List
|
||||
|
||||
class Lexer {
|
||||
fun lex(input: String): List<Token> {
|
||||
val tokens = mutableListOf<Token>()
|
||||
|
@ -14,8 +16,20 @@ class Lexer {
|
|||
val iterator = PeekableCharIterator(line.toCharArray())
|
||||
while (iterator.hasNext()) {
|
||||
when (val next = iterator.next()) {
|
||||
'#' -> header(iterator, tokens)
|
||||
'>' -> quote(iterator, tokens)
|
||||
'#', '#' -> header(iterator, tokens)
|
||||
'>', '>' -> quote(iterator, tokens)
|
||||
'-', '=', 'ー', '=' -> {
|
||||
if (iterator.peekOrNull()?.isWhitespace() == true) { //-の直後がスペースならリストの可能性
|
||||
list(iterator, tokens, next)
|
||||
} else {//それ以外ならセパレーターの可能性
|
||||
separator(next, iterator, tokens)
|
||||
}
|
||||
}
|
||||
|
||||
' ', ' ' -> {
|
||||
tokens.add(Whitespace(skipWhitespace(iterator) + 1, next)) //nextの分1足す
|
||||
}
|
||||
|
||||
else -> {
|
||||
tokens.add(Text(next + collect(iterator)))
|
||||
tokens.add(Break(1))
|
||||
|
@ -39,6 +53,61 @@ class Lexer {
|
|||
return tokens
|
||||
}
|
||||
|
||||
private fun list(
|
||||
iterator: PeekableCharIterator,
|
||||
tokens: MutableList<Token>,
|
||||
next: Char
|
||||
) {
|
||||
|
||||
if (iterator.peekOrNull()?.isWhitespace() == true) {
|
||||
tokens.add(DiscList)
|
||||
}
|
||||
|
||||
skipWhitespace(iterator)
|
||||
if (iterator.peekOrNull() == '[') {
|
||||
iterator.next()
|
||||
val checkedChar = iterator.peekOrNull() ?: return
|
||||
iterator.next()
|
||||
if ((checkedChar == 'x' || checkedChar == ' ' || checkedChar == ' ').not()) {
|
||||
tokens.add(Text("[$checkedChar"))
|
||||
return
|
||||
}
|
||||
val checked = checkedChar == 'x'
|
||||
if (iterator.peekOrNull() == ']') {
|
||||
iterator.next()
|
||||
if (iterator.peekOrNull()?.isWhitespace() == true) {
|
||||
iterator.next()
|
||||
tokens.add(CheckBox(checked))
|
||||
return
|
||||
}
|
||||
}
|
||||
tokens.add(Text("[$checkedChar"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun separator(
|
||||
next: Char,
|
||||
iterator: PeekableCharIterator,
|
||||
tokens: MutableList<Token>
|
||||
) {
|
||||
val builder = StringBuilder()
|
||||
builder.append(next)
|
||||
|
||||
while (iterator.peekOrNull() == next) {
|
||||
builder.append(iterator.next())
|
||||
}
|
||||
if (iterator.peekOrNull() == null && builder.length >= 3) { //行末まで到達していてかつ長さが3以上か
|
||||
tokens.add(Separator(builder.length, next)) //セパレーターとして追加
|
||||
} else {
|
||||
val token = tokens.lastOrNull() //ただの文字として追加
|
||||
if (token is Text) {
|
||||
tokens[tokens.lastIndex] = Text(token.text + builder.toString())
|
||||
} else {
|
||||
tokens.add(Text(builder.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun quote(
|
||||
iterator: PeekableCharIterator,
|
||||
tokens: MutableList<Token>
|
||||
|
@ -49,7 +118,7 @@ class Lexer {
|
|||
count++
|
||||
}
|
||||
tokens.add(Quote(count))
|
||||
iterator.next() //スペースを無視
|
||||
skipWhitespace(iterator)
|
||||
tokens.add(Text(collect(iterator)))
|
||||
tokens.add(Break(1))
|
||||
}
|
||||
|
@ -64,11 +133,20 @@ class Lexer {
|
|||
count++
|
||||
}
|
||||
tokens.add(Header(count))
|
||||
iterator.next() //スペースを無視
|
||||
skipWhitespace(iterator)
|
||||
tokens.add(Text(collect(iterator)))
|
||||
tokens.add(Break(1))
|
||||
}
|
||||
|
||||
fun skipWhitespace(iterator: PeekableCharIterator): Int {
|
||||
var count = 0
|
||||
while (iterator.peekOrNull()?.isWhitespace() == true) {
|
||||
iterator.next()
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
fun collect(iterator: PeekableCharIterator): String {
|
||||
val char = mutableListOf<Char>()
|
||||
while (iterator.hasNext()) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package dev.usbharu.markdown
|
||||
|
||||
import kotlin.collections.List
|
||||
|
||||
class PeekableCharIterator(private val charArray: CharArray) : Iterator<Char> {
|
||||
private var index = 0
|
||||
override fun hasNext(): Boolean = index < charArray.size
|
||||
|
|
|
@ -5,4 +5,16 @@ sealed class Token()
|
|||
data class Text(var text: String) : Token()
|
||||
data class Break(var count: Int) : Token()
|
||||
data class Header(var count: Int) : Token()
|
||||
data class Quote(var count: Int) : Token()
|
||||
data class Quote(var count: Int) : Token()
|
||||
data class Separator(var count: Int, val char: Char) : Token()
|
||||
data class Whitespace(var count: Int, val whitespace: Char) : Token()
|
||||
abstract class List(val type: ListType) : Token() {
|
||||
enum class ListType {
|
||||
DISC,
|
||||
DECIMAL
|
||||
}
|
||||
}
|
||||
|
||||
data object DiscList : List(ListType.DISC)
|
||||
data class DecimalList(val number: Int) : List(ListType.DECIMAL)
|
||||
data class CheckBox(val checked:Boolean): Token()
|
|
@ -16,6 +16,17 @@ class LexerTest {
|
|||
assertContentEquals(listOf(Break(1)), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun 改行2() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("\r\n")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Break(1)), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun 複数の改行() {
|
||||
val lexer = Lexer()
|
||||
|
@ -82,6 +93,29 @@ class LexerTest {
|
|||
assertContentEquals(listOf(Header(2), Text("abcd efgh")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ヘッダー後の空白は無視() {
|
||||
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("## abcd efgh")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Header(2), Text("abcd efgh")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun 全角ヘッダー() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("# abcd efgh")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Header(1), Text("abcd efgh")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ヘッダーの中にヘッダー`() {
|
||||
val lexer = Lexer()
|
||||
|
@ -125,4 +159,176 @@ class LexerTest {
|
|||
|
||||
assertContentEquals(listOf(Quote(2), Text(">abcd")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun 全角引用() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("> >abcd")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Quote(1), Text(">abcd")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun 引用後の空白は無視() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex(">> >abcd")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Quote(2), Text(">abcd")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun セパレーター() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("---")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Separator(3, '-')), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun セパレーター2() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("===")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Separator(3, '=')), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun セパレーター混在() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("-=-")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Text("-=-")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun セパレーターかと思ったら本文だった() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("---aiueo")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Text("---"), Text("aiueo")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun チェックボックス() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("- [x] a")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(DiscList, CheckBox(true), Text("a")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun チェックボックス2() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("- [ ] a")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(DiscList, CheckBox(false), Text("a")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun チェックボックスかと思ったら違った() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("- [xa a")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(DiscList, Text("[x"), Text("a a")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun チェックボックスかと思ったら違った2() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("- [a a")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(DiscList, Text("[a"), Whitespace(1, ' '), Text("a")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun チェックボックスかと思ったら違った3() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("-aiueo")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Text("-"), Text("aiueo")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun チェックボックスいっぱい() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("- [ ] a\n- [x] b\n- [ ] c\n- [x] d")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(
|
||||
listOf(
|
||||
DiscList,
|
||||
CheckBox(false),
|
||||
Text("a"),
|
||||
Break(1),
|
||||
DiscList,
|
||||
CheckBox(true),
|
||||
Text("b"),
|
||||
Break(1),
|
||||
DiscList,
|
||||
CheckBox(false),
|
||||
Text("c"),
|
||||
Break(1),
|
||||
DiscList,
|
||||
CheckBox(true),
|
||||
Text("d"),
|
||||
), actual
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ディスクリスト() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("- aiueo")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(DiscList, Text("aiueo")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ディスクリストいっぱい() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("- aiueo\n- abcd")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(DiscList, Text("aiueo"), Break(1), DiscList, Text("abcd")), actual)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue