uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

どう書く.org課題「 ローカル変数の一覧を取得」

どう書くorg課題「ローカル変数の一覧を取得」を書いてみました。

対象を今回クロージャに限定しています。ふつー、JVM上で動く言語としてのJavaでもGroovyでも、ローカル変数名はコンパイル後には消え去っているので、こういうことはできません。なので、今回ASTを引っ張ってくる裏口の1つを使っています。GSQLでwhere句やorder句で行っているのと同じ方法です。具体的に何をしているかというと裏でgroovyソースを再度コンパイルしてASTを取ってくるということをしているのです。

こういう変なことをしなくても、いまやAST変換のAPIが充実しているので、もそっときれいに書くことはできると思います。ただ、今回に関しては、ローカル変数にアノテーションをつけてもAST変換のマーカーとして起動してくれないという問題がありまして、関数一般に効くものは作りませんでした。

import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.*;

csv = new CodeVisitorSupport() {
  Map vars = [:];
  void visitDeclarationExpression(DeclarationExpression expr) {
    vars[expr.leftExpression.name] = expr.rightExpression.value
  }
};


def localVars(Closure closure) {
  if (closure != null) {
    ClassNode classNode = closure.getMetaClass().getClassNode();
    if (classNode == null) {
      throw new GroovyRuntimeException("Could not find the ClassNode for MetaClass: " + closure.getMetaClass());
    }
    List methods = classNode.getDeclaredMethods("doCall");
    if (!methods.isEmpty()) {
      MethodNode method = (MethodNode) methods.get(0);
      if (method != null) {
        Statement statement = method.getCode();
        if (statement != null) {
          statement.visit(csv);
        }
      }
    }
  }
  return csv.vars
}

foo = {
  def x = 1
  def y = "hello"
  result = localVars(clone())
  return result
}

foo().each{key,value->
  println "$key => $value"
}