最近では、
コンパイル時AST変換などの強力なGroovy機能を頻繁に使用する必要があります。
私は過度のダイナミクスが好きではないので、私たちが持っているDSL検証チェックのほとんどはコンパイル段階で行われ、多くのコード生成も使用します。 したがって、毎日手動でASTNode-sをコンパイルする必要があります。
def someVariable = new ConstantExpression("someValue"); def returnStatement = new ReturnStatement( new ConstructorCallExpression( ClassHelper.make(SomeCoolClass), new ArgumentListExpression(someVariable) ) );
痛々しいほど馴染みのあるデザインですね。 このようにしたいですか?
def someVariable = macro { "someValue" } def returnStatement = macro { return new SomeCoolClass($v{ someVariable }) }
それともそうですか?
def constructorCall = macro { new SomeCoolClass($v{ macro { "someValue" } }) }
この記事では、Groovyのネイティブソリューションである
github.com/bsideup/MacroGroovyにできるだけ近い、この問題に対する私のソリューションについて説明し
ます。アストビルダー
Groovy 1.7は、
AstBuilderのような一見すばらしいものを
もたらしました 。これは、ASTを構築する3つの方法を提供します。
AstBuilder.buildFromString
コードを含む行を渡すと、出力にASTNode-sのリストがあります。
List<ASTNode> nodes = new AstBuilder().buildFromString("\"Hello\"")
メリット
- 入力-文字列、どこからでも取得できます。
- ASTNode-sの配置方法を理解する必要はありません。
- CompilePhaseを指定できます。
- ほぼ100%有効なコードを生成します。
- 信頼性-GroovyのASTNode-sの構造が変更された場合、コードを変更する必要はありません。
欠点
- IDEは構文チェックを支援しません。
- IDEでのリファクタリングも機能しません。
- 一部のエンティティは作成できません-たとえば、クラスフィールドを宣言します。
これらの欠点の一部は、次の方法を修正することを目的としています。
AstBuilder.buildFromCode
コードでクロージャー(別名Closure)を渡します。出力にはノードのリストがあります。
List<ASTNode> nodes = new AstBuilder().buildFromCode { "Hello" }
利点(前の方法の利点以外)
- IDEを使用すると、クロージャでオートコンプリート、構文チェック、およびリファクタリングを使用できます。
短所:
両方の方法に欠けている人には、3番目の方法があります。
AstBuilder.buildFromSpec
このメソッドは、ASTを構築するためのDSLであるクロージャーを取得します(ところで、私の課題に投票するか、 プルリクエストにコメントして、このメソッドに素晴らしいDelegatesToアノテーションを表示できます)。
List<ASTNode> nodes = new AstBuilder().buildFromSpec { block { returnStatement { constant "Hello" } } }
メリット
- Groovyロジックを使用してノードを構築できます。
- 既存のほぼすべてのASTNodeを構築する機能を提供します。
- 重要なプラスとして、 GroovyのAST生成トピックは十分に文書化されていません。完全に文書化されており、 TestCaseでの広範なユースケースがあります。
欠点
- 目的の結果を得るために何を呼び出す必要があるかを正確に理解することが難しい場合があります。
- ノードコンストラクターの呼び出しほど冗長ではありませんが、それでも変わりません。
- 奇妙な実装-たとえば、一部のメソッドはClassNodeの代わりにClassを受け入れ、その使用を無効にします。
- 信頼性の低い-ASTは主要な言語リリースで変更される可能性があります。
- 特定のコンパイルフェーズでASTがどのように見えるかを正確に知る必要があります。
- これまでの IDE(プルリクエストに関する私のコメントを参照)は、このDSLのオートコンプリートをサポートしていません。
方法の組み合わせ
これらの方法を組み合わせることができることも言及する価値があります。
List<ASTNode> result = new AstBuilder().buildFromSpec { method('myMethod', Opcodes.ACC_PUBLIC, String) { parameters { parameter 'parameter': String.class } exceptions {} block { owner.expression.addAll new AstBuilder().buildFromCode { println 'Hello from a synthesized method!' println "Parameter value: $parameter" } } annotations {} } }
MacroGroovy
したがって、可能性についての広範なレビューの後、次のように尋ねることができます。など... * ahem * ... fig MacroGroovyが必要ですか?
投稿ヘッダーの例を考えてみましょう。
def someVariable = new ConstantExpression("someValue"); def returnStatement = new ReturnStatement( new ConstructorCallExpression( ClassHelper.make(SomeCoolClass), new ArgumentListExpression(someVariable) ) );
引数リストコンストラクターに渡されるsomeVariableを参照してください。 私を信じて、この状況は非常に一般的です。 そして、彼女はすぐにbuildFromCodeとbuildFromStringをスイープします。 buildFromSpecのみが残りますが、その欠点のリストを覚えていますか? これがMacroGroovyの助けとなります。
def someVariable = macro { "someValue" }; def returnStatement = macro { return new SomeCoolClass($v{ someVariable }) }
メリット欠点- 残念ながら、マクロ{}を使用してクラスフィールドを作成することもできません。
- CompilePhaseの可能性はありません。
ところで、buildFromSpecとマクロを組み合わせることができます:
List<ASTNode> result = new AstBuilder().buildFromSpec { method('myMethod', Opcodes.ACC_PUBLIC, String) { parameters { parameter 'parameter': String.class } exceptions {} block { owner.expression.addAll macro { println 'Hello from a synthesized method!' println "Parameter value: $parameter" } } annotations {} } }
テストへのリンクを残します。これは、MacroGroovyがコードの量を時々減らす方法を示しています。
github.com/bsideup/MacroGroovy/blob/master/example/basicExample/src/test/groovy/ru/trylogic/groovy/macro/examples/basic/BasicTest.groovyおわりに
それぞれの方法には長所と短所があり、私は他の方法の欠点をなくすように努めました。 プルリクエストのテストにご協力いただきありがとうございます。
ライブラリはMaven Centralで利用できます。常に最新バージョンを見つけることができるリンクを残します。
search.maven.org/#search%7Cga%7C1%7Cmacro-groovyありがとう