コードブロックに対応

This commit is contained in:
usbharu 2024-11-14 11:46:45 +09:00
parent 4c23bdc1ad
commit 985df8ed82
Signed by: usbharu
GPG Key ID: 95CBCF7046307B77
3 changed files with 230 additions and 3 deletions

View File

@ -8,6 +8,9 @@ class Lexer {
val lines = PeekableStringIterator(input.lines()) val lines = PeekableStringIterator(input.lines())
var inQuote = false var inQuote = false
var inCode = false
val codeBuffer = StringBuilder()
line@ while (lines.hasNext()) { line@ while (lines.hasNext()) {
@ -20,6 +23,68 @@ class Lexer {
char@ while (iterator.hasNext()) { char@ while (iterator.hasNext()) {
val next = iterator.next() val next = iterator.next()
when { when {
next == '`' || next == '' -> {
//todo ````` のようなやつが来たときのことを考える
if (iterator.peekOrNull() == next) {
val codeBlockBuilder = StringBuilder()
codeBlockBuilder.append(next)
codeBlockBuilder.append(iterator.next())
if (iterator.peekOrNull() == next) {
codeBlockBuilder.append(iterator.next())
if (iterator.peekOrNull() == next) {
tokens.add(Text(codeBlockBuilder.toString()))
} else {
if (inCode) {
inCode = false
tokens.add(CodeBlock(codeBuffer.toString().trimStart('\n').trimEnd('\n')))
codeBuffer.clear()
} else {
inCode = true
var inFilename = false
val language = StringBuilder()
val filename = StringBuilder()
if (iterator.hasNext()) {
codeBlock@ while (iterator.hasNext()) {
val nextLanguage = iterator.next()
if ((nextLanguage == ':' || nextLanguage == '') && !inFilename) {
inFilename = true
continue@codeBlock
}
if (inFilename) {
filename.append(nextLanguage)
} else {
language.append(nextLanguage)
}
}
tokens.add(CodeBlockLanguage(language.toString(), filename.toString()))
}
}
}
} else if (iterator.peekOrNull() == null) {
tokens.add(Text(codeBlockBuilder.toString()))
}
} else {
val codeBuilder = StringBuilder()
while (iterator.hasNext() && iterator.peekOrNull() != next) {
codeBuilder.append(iterator.next())
}
if (iterator.hasNext() && iterator.next() == next) { //インラインコードブロックかと思ったら違った
tokens.add(InlineCodeBlock(codeBuilder.toString()))
} else {
tokens.add(Text(codeBuilder.insert(0, next).toString()))
}
}
}
inCode -> {
codeBuffer.append(next)
}
next == '#' || next == '' -> header(iterator, tokens) next == '#' || next == '' -> header(iterator, tokens)
(next == '>' || next == '') && !inQuote -> { (next == '>' || next == '') && !inQuote -> {
inQuote = true inQuote = true
@ -58,6 +123,7 @@ class Lexer {
} }
} }
else -> { else -> {
val lastToken = tokens.lastOrNull() val lastToken = tokens.lastOrNull()
if (lastToken is Text) { if (lastToken is Text) {
@ -69,7 +135,12 @@ class Lexer {
} }
} }
tokens.add(Break(1))
if (inCode) {
codeBuffer.append("\n")
} else {
tokens.add(Break(1))
}
} }
inQuote = false inQuote = false
} }
@ -137,9 +208,10 @@ class Lexer {
while (iterator.hasNext() && iterator.peekOrNull() != doubleQuotation && iterator.peekOrNull() != ')') { while (iterator.hasNext() && iterator.peekOrNull() != doubleQuotation && iterator.peekOrNull() != ')') {
titleBuilder.append(iterator.next()) titleBuilder.append(iterator.next())
} }
if (iterator.peekOrNull() == '"') { if (iterator.peekOrNull() == doubleQuotation) {
iterator.next() iterator.next()
} }
tokens.add(UrlTitle(titleBuilder.toString()))
} }
} }
} }

View File

@ -25,4 +25,7 @@ data object ParenthesesEnd : Token()
data class Url(var url: String) : Token() data class Url(var url: String) : Token()
data class Asterisk(var count: Int, var char: Char) : Token() data class Asterisk(var count: Int, var char: Char) : Token()
data object Exclamation : Token() data object Exclamation : Token()
data class UrlTitle(val title: String) : Token() data class UrlTitle(val title: String) : Token()
data class InlineCodeBlock(val text: String) : Token()
data class CodeBlock(val text: String) : Token()
data class CodeBlockLanguage(val language: String, val filename: String) : Token()

View File

@ -608,4 +608,156 @@ class LexerTest {
), actual ), actual
) )
} }
@Test
fun urlとタイトル() {
val lexer = Lexer()
val actual = lexer.lex("[alt](https://example.com \"example\")")
println(actual)
assertContentEquals(
listOf(
SquareBracketStart,
Text("alt"),
SquareBracketEnd,
ParenthesesStart,
Url("https://example.com"),
UrlTitle("example"),
ParenthesesEnd
), actual
)
}
@Test
fun urlとタイトル全角() {
val lexer = Lexer()
val actual = lexer.lex("[alt](https://example.com \"example)")
println(actual)
assertContentEquals(
listOf(
SquareBracketStart,
Text("alt"),
SquareBracketEnd,
ParenthesesStart,
Url("https://example.com"),
UrlTitle("example"),
ParenthesesEnd
), actual
)
}
@Test
fun インラインコードブロック() {
val lexer = Lexer()
val actual = lexer.lex("`code`")
println(actual)
assertContentEquals(
listOf(
InlineCodeBlock("code"),
), actual
)
}
@Test
fun インラインコードブロック2() {
val lexer = Lexer()
val actual = lexer.lex("aiueo`code`abcd")
println(actual)
assertContentEquals(
listOf(
Text("aiueo"),
InlineCodeBlock("code"),
Text("abcd"),
), actual
)
}
@Test
fun コードブロック() {
val lexer = Lexer()
val actual = lexer.lex(
"""```
|code
|```
""".trimMargin()
)
println(actual)
assertContentEquals(
listOf(
CodeBlock("code"),
), actual
)
}
@Test
fun 言語指定付きコードブロック() {
val lexer = Lexer()
val actual = lexer.lex(
"""```hoge
|code
|```
""".trimMargin()
)
println(actual)
assertContentEquals(
listOf(
CodeBlockLanguage("hoge", ""),
CodeBlock("code"),
), actual
)
}
@Test
fun ファイル名と言語コードブロック() {
val lexer = Lexer()
val actual = lexer.lex(
"""```hoge:fuga
|code
|```
""".trimMargin()
)
println(actual)
assertContentEquals(
listOf(
CodeBlockLanguage("hoge", "fuga"),
CodeBlock("code"),
), actual
)
}
@Test
fun コードブロックかと思ったら違った() {
val lexer = Lexer()
val actual = lexer.lex("````aiueo")
println(actual)
assertContentEquals(
listOf(
Text("```"), Text("`aiueo")
), actual
)
}
} }