Groovyで簡単なS式をパースするサンプル
無駄に複雑なコードになってしまっていますが、一応四則演算は実行できるS式のパーサが出来ました。
間違いなくもっとスマートな方法があるはずですが(普通にググれば10行程度でRubyで実装している方もいらっやいます)、今まで基本的にWebプログラミングしかして来なかったのでこういったパーサ系なんかも全部ライブラリ任せにしてしまってたので、まずは自分のための勉強ということで。。。
これを機会にこういったまた普段とは違ったコードも書いていきたいなと思います。
trait Parser { Closure findLastList = {l, next -> if (next <= 0 || l == [] || !(l instanceof List)) { l } else { call(l.last(), next-1) } } Closure normalizeSpaces = {String str -> str = str.replaceAll(/\(/, ' ( ') str = str.replaceAll(/\)/, ' ) ') // 先頭か末尾にスペースがある場合は1個に正規化されるけど必要ないのでtim() str.replaceAll(/\s{1,}/, ' ' ).trim() } Closure parseToList = {List<String> tokens -> List list = [] Integer nest = 0 tokens.each { if (it == '(') { if (nest > 1) { findLastList(list, nest-1) << [] } else { list << [] } nest++ } else if (it == ')') { nest-- } else { if (nest > 1) { findLastList(list, nest-1) << it } else { list << it } } } list.tail() } List<String> parse(String sourceCode) { String normalized = normalizeSpaces(sourceCode) List<String>tokens = normalized.tokenize(' ') parseToList(tokens) } } class MyLanguage implements Parser { Map operations = [ '+': {a, b -> (a as Integer) + (b as Integer)}, '-': {a, b -> (a as Integer) - (b as Integer)}, '*': {a, b -> (a as Integer) * (b as Integer)}, '/': {a, b -> (a as Integer) / (b as Integer)}, ] Closure execute = {List code -> String operation = code.head() List<String> operands = code.tail() def o = [] for (obj in operands) { o << ( (obj instanceof List) ? call(obj) : obj ) } o.inject(operations[operation]) } def eval(String sourceCode) { List<String> parsedAsList = parse(sourceCode) execute(parsedAsList) } } // 以下のように、ネストしたS式でもちゃんと四則演算ができている。 def engine = new MyLanguage() assert engine.eval('( + 1 2 3 ( - 1 2 ) )') == 5 assert engine.eval('(* 3 5)') == 15 assert engine.eval('(/ 20 2 5)') == 2 assert engine.eval('(* 2 (/ 10 5))') == 4 assert engine.eval('(+ (* 2 4) 5)') == 13 assert engine.eval('(- (+ (* 2 4) 5 (/ 6 2)) 10)') == 6