
最近では、 コンパイル時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を使用すると、クロージャでオートコンプリート、構文チェック、およびリファクタリングを使用できます。
短所:
- この方法では、多数のエンティティを生成できないという問題は解決されません。
- コードをコンパイルします。そのため、トリッキーな構造や存在しないクラスを常に使用できるとは限りません。
- 私にとっての主な欠点:buildFromCodeを呼び出すには 、AstBuilderを作成してメソッドを呼び出す必要があります。
new AstBuilder().buildFromCode { ... }
同時に、AstBuilderを別のフィールドまたはローカル変数に配置することもできません(したがって、Groovyの作成者は、このAstTransformationのAstTransformationを使用して、多くのコードを記述しないようにする必要がありました)
両方の方法に欠けている人には、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 }) }
メリット
- 1番目と2番目の方法のすべての利点。
- マクロメソッドが呼び出されるオブジェクトを作成する必要はありません。すべてのクラスで拡張メソッドとして使用できます。
- AstBuilderのコードを内部で再利用するため、メソッドは信頼性が高くテスト済みです。
- コード内でGroovyロジックを使用できます。 $ vはクロージャを受け入れます。クロージャは、呼び出しの代わりに配置する必要があるものを返す必要があります。
- 非常に、非常にコンパクト:)比較:
macro { return mySuperVariable }
(new AstBuilder()).buildFromCode { return mySuperVariable }.first().expressions.first()
欠点
- 残念ながら、マクロ{}を使用してクラスフィールドを作成することもできません。
- 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
ありがとう