Kotlin に入門しています。
まずは何か適当なものを作りながら慣れようということで、四則演算と剰余のみのexprコマンドをRubyで作ってみた を移植してみました。手書きの再帰下降パーサです。
# (100 - 2 - 1) / (1 + 2) % 5 * 3 # => 97 / 3 % 5 * 3 # => 32 % 5 * 3 # => 2 * 3 # => 6 ## 実行の例 $ kotlin MyExpr.kts -- \( 100 - 2 - 1 \) / \( 1 + 2 \) % 5 \* 3 6 ## 確認のため同じ引数で expr コマンドを実行 $ expr \( 100 - 2 - 1 \) / \( 1 + 2 \) % 5 \* 3 6
// MyExpr.kts enum class Op(val symbol: String) { ADD("+"), SUB("-"), MUL("*"), DIV("/"), MOD("%") } abstract class Node () { abstract fun eval(): Int } class NumberNode (val n: Int) : Node() { override fun eval(): Int = this.n } class BinopNode ( val op: Op, val left: Node, val right: Node ) : Node() { override fun eval(): Int { return ( when (this.op) { Op.ADD -> this.left.eval() + this.right.eval() Op.SUB -> this.left.eval() - this.right.eval() Op.MUL -> this.left.eval() * this.right.eval() Op.DIV -> this.left.eval() / this.right.eval() Op.MOD -> this.left.eval() % this.right.eval() } ) } } class Parser (val tokens: List<String>) { final val NUMERIC_CHARS = setOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') var cur = 0 // -------------------------------- class ParseException(msg: String) : RuntimeException(msg) fun currentToken(): String = this.tokens.get(this.cur) fun isAdditive(): Boolean { if (this.tokens.size <= this.cur) { // end of tokens return false } return setOf("+", "-").contains(currentToken()) } fun isMultiply(): Boolean { if (this.tokens.size <= this.cur) { // end of tokens return false } return setOf("*", "/", "%").contains(currentToken()) } fun consume(token: String, exception: Boolean = false): Boolean { if (currentToken() == token) { this.cur += 1 return true } else { if (exception) { throw ParseException("expected <${token}> / got <${currentToken()}>") } return false } } // -------------------------------- fun parse(): Node = parseExpression() fun parseExpression(): Node = parseAdditive() fun parseAdditive(): Node { var node = parseMultiply() while (isAdditive()) { val (op, multiply) = parseAdditiveTail() node = BinopNode(op, node, multiply) } return node } fun parseAdditiveTail(): Pair<Op, Node> { val op = when { consume("+") -> Op.ADD consume("-") -> Op.SUB else -> { throw ParseException("expected '+' or '-' / got <${currentToken()}>") } } return Pair(op, parseMultiply()) } fun parseMultiply(): Node { var node = parseFactor() while (isMultiply()) { val (op, factor) = parseMultiplyTail() node = BinopNode(op, node, factor) } return node } fun parseMultiplyTail(): Pair<Op, Node> { val op = when { consume("*") -> Op.MUL consume("/") -> Op.DIV consume("%") -> Op.MOD else -> { throw ParseException("expected '*', '/' or '%' / got <${currentToken()}>") } } return Pair(op, parseFactor()) } fun parseFactor(): Node { if (consume("(")) { val exp = parseExpression() consume(")", true) return exp } else { return parseNumber() } } fun parseNumber(): NumberNode { val token = currentToken() this.cur += 1 if (isNumber(token)) { return NumberNode( Integer.valueOf(token) ) } else { throw ParseException("invalid number (${token})") } } fun isNumber(token: String): Boolean { val firstIndex = if (token.get(0) == '-') { 1 } else { 0 } for (i in firstIndex .. (token.length - 1)) { val c = token.get(i) if (! NUMERIC_CHARS.contains(c)) { return false } } return true } } // -------------------------------- val tokens = args.toList() val tree = Parser(tokens).parse() val result = tree.eval() println(result)