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