プログラミング2(Kotlin+字句解析器)

勉強がてら2パターン試しました。
Tokenを返すメソッドの内容が少し違いますが、基本的な動作は一緒です。
Main.kt : メインループ
Token.kt : トークンタイプとリテラル(読み取った内容)のdata class
TokenType.kt : トークンタイプを示すenum class
Lexer.kt : 字句解析器(入力に対してTokenを返す)

forを使用した場合

Main.kt

fun main() {

    val input ="1+2*3/4-5"

    println(input)
    val l=Lexer(input)
    input.forEach {
        val tmp = l.nextToken()
        println("$it:${tmp.literal},${tmp.tokenType}")
    }
}

Token.kt

data class Token(val tokenType: TokenType, val literal: String)

TokenType.kt

enum class TokenType {
    ILLEGAL,
    EOF,

    INT, //整数

    //演算子
    PLUS, // +
    MINUS, // -
    ASTERISK, // *
    SLASH, // /
}

Lexer.kt

import kotlin.properties.Delegates

class Lexer(private val input: String) {

    private var position: Int = 0
    private var currentChar: Char by Delegates.notNull()
    private var nextChar: Char by Delegates.notNull()

    init {
        readChar()
    }

    private fun readChar() {
        currentChar = if (position >= input.length) {
            0.toChar()
        } else {
            input[position]
        }
        nextChar = if (position + 1 >= input.length) {
            0.toChar()
        } else {
            input[position + 1]
        }
        position++
    }


    private fun readNumber(): String {
        var number: String = currentChar.toString()
        while (isDigit(nextChar)) {
            number += nextChar
            readChar()
        }
        return number
    }

    private fun skipWhiteSpace() {
        while ((currentChar == ' ') || (currentChar == '\t') || (currentChar == '\r') || (currentChar == 'n')) {
            readChar()
        }
    }

    fun nextToken(): Token {
        skipWhiteSpace()
        var token: Token by Delegates.notNull()
        when (currentChar) {
            '+' -> {
                token = Token(TokenType.PLUS, currentChar.toString())
            }
            '-' -> {
                token = Token(TokenType.MINUS, currentChar.toString())
            }
            '*' -> {
                token = Token(TokenType.ASTERISK, currentChar.toString())
            }
            '/' -> {
                token = Token(TokenType.SLASH, currentChar.toString())
            }
            else -> {
                token = if (isDigit(currentChar)) {
                    val number = readNumber()
                    Token(TokenType.INT, number)
                } else {
                    Token(TokenType.ILLEGAL, currentChar.toString())
                }
            }
        }
        readChar()
        return token
    }

    private fun isDigit(c: Char): Boolean {
        return (c in '0'..'9')
    }
}

Iteratorを使用した場合

Main.kt

fun main() {
    val input = "+-*/===1234"
    val l = Lexer(input)
    val e = l.iterator()

    println("input : $input")
    while (e.hasNext()) {
        val tmp = e.next()
        println("TokenType: ${tmp.tokenType}, Literal: ${tmp.literal}")
    }
}

Token.kt

data class Token(val tokenType: TokenType, val literal: String)

TokenType.kt

enum class TokenType {
    ILLEGAL,
    EOF,

    INT, //整数

    //演算子
    PLUS, // +
    MINUS, // -
    ASTERISK, // *
    SLASH, // /
}

Lexer.kt

class Lexer(private val input: String) : Iterator<Token> {
    var position = 0

    override fun hasNext(): Boolean {
        return position < input.length
    }

    override fun next(): Token {
        val token: Token
        skipWhiteSpace()
        when (input[position]) {
            '+' -> {
                token = Token(TokenType.PLUS, input[position].toString())
            }
            '-' -> {
                token = Token(TokenType.MINUS, input[position].toString())
            }
            '*' -> {
                token = Token(TokenType.ASTERISK, input[position].toString())
            }
            '/' -> {
                token = Token(TokenType.SLASH, input[position].toString())
            }
            '=' -> {
                if (input[position + 1] == '=') {
                    token = Token(TokenType.EQ, "==")
                    position++
                } else {
                    token = Token(TokenType.ASSIGN, input[position].toString())
                }
            }
            else -> {
                token = if (readDigit(input[position])) {
                    val number = readNumber()
                    Token(TokenType.INT, number)
                } else {
                    Token(TokenType.ILLEGAL, input[position].toString())
                }
            }
        }
        position++
        return token
    }

    private fun readDigit(c: Char): Boolean {
        return (c in '0'..'9')
    }

    private fun readNumber(): String {
        var number: String = input[position].toString()
        if (lengthCheck()) return number
        while (readDigit(input[position + 1])) {
            number += input[position + 1]
            position++
            if (lengthCheck()) break
        }
        return number
    }

    private fun skipWhiteSpace(){
        while(input[position]== ' ' || input[position]=='\t' || input[position]=='\r' || input[position]=='\n')
            position++
    }

    private fun lengthCheck():Boolean{
        return position + 1 >= input.length
    }
}

入力と出力

input : +-*/= = =1234
TokenType: PLUS, Literal: +
TokenType: MINUS, Literal: -
TokenType: ASTERISK, Literal: *
TokenType: SLASH, Literal: /
TokenType: ASSIGN, Literal: =
TokenType: ASSIGN, Literal: =
TokenType: ASSIGN, Literal: =
TokenType: INT, Literal: 1234

一言

Iteratorを使うか使わないかの違い。
前回Iteratorパターンを勉強したので早速使ってみた。
字句解析器は先読みが重要みたいなので、前者のパターンの方が良いのかも。
事細かにtestをしてないのでバグありそう。