使用 Kotlin 协程实现可挂起的 parser

171 阅读1分钟

文章灵感与参考来自于 用宏实现的极简协程库 中字符串解析的事例

parser 每消费一个字符时,由上次挂起处继续向后顺序执行,例如解析 GET /root/path HTTP/1.1 时,遇到第二个空格时不会走入第一个空格的后续分支

相比使用状态机进行解析,借助协程可以用更简单更顺序化的逻辑

fun main() {
    val str = "GET /root/path HTTP/1.1"

    val parser = SuspendParser()

    for (char in str) {
        parser.resume(char)
    }

    parser.end()
    println(parser.result())
}

class SuspendParser {

    private val method = LinkedList<Char>()
    private val path = LinkedList<Char>()
    private val version = LinkedList<Char>()

    private var co: Continuation<Char> = Continuation(EmptyCoroutineContext) {
        val block: suspend Char.() -> Unit = ::parse
        block.startCoroutine(
            receiver = it.getOrThrow(),
            completion = Continuation(EmptyCoroutineContext) { println("all end") }
        )
    }

    private suspend fun parse(first: Char) {

        var char = first

        while (char.isAlpha()) {
            method += char
            char = next()
        }

        if (char == ' ') {
            char = next()
        }

        while (char.isAlpha() || char.isSlash()) {
            path += char
            char = next()
        }

        if (char == ' ') {
            char = next()
        }

        while (char.isAlpha() || char.isSlash() || char.isNum() || char.isDot()) {
            version += char
            char = next()
        }
    }

    private suspend fun next(): Char {
        return suspendCoroutine { co = it }
    }

    fun resume(c: Char) {
        co.resume(c)
    }

    fun end() {
        co.resume('$') // make co all end by a nothing key word
    }

    fun result(): String {
        val m = method.joinToString(separator = "")
        val p = path.joinToString(separator = "")
        val v = version.joinToString(separator = "")

        return "method = $m path = $p version = $v"
    }
}

fun Char.isNum(): Boolean = this in '0'..'9'

fun Char.isSlash(): Boolean = this == '/'

fun Char.isDot(): Boolean = this == '.'

fun Char.isAlpha(): Boolean = isAlphaLow() || isAlphaUp()

fun Char.isAlphaUp(): Boolean = this in 'A'..'Z'

fun Char.isAlphaLow(): Boolean = this in 'a'..'z'