Groovy は AST 変換の実装用に AST を生成する DSL を提供しています。ところがこの DSL は複雑で習得が困難です。次のような GString の AST を生成したいとします:
"Hi, $name."その場合、DSL はこうなります。こんなもの構文と同じ数覚えられません:
gString 'Hi, $name.', { strings { constant 'Hi, ' constant '.' } values { variable 'name' } }
ソースコードから直接生成することもできますが、コンパイラによるチェックの恩恵を受けられないですし (これについては Joachim Baumann が説明しています)、DSL よりパフォーマンスが悪いです。次のベンチマークを見てください:
@Grab('com.googlecode.gbench:gbench:0.2.2') import gbench.BenchmarkBuilder import org.codehaus.groovy.ast.builder.AstBuilder def benchmarks = new BenchmarkBuilder().run { 'DSL to AST' { new AstBuilder().buildFromSpec { gString 'Hi, $name.', { strings { constant 'Hi, ' constant '.' } values { variable 'name' } } } } 'Code to AST' { new AstBuilder().buildFromString('"Hi, $name"') } } benchmarks.prettyPrint()
user system cpu real DSL to AST 0 0 0 339918 Code to AST 0 0 0 2076590というわけで、DSL を実装コードやテストコードを見ながら書く羽目になるのですが、これが大変な作業になることは簡単に想像してもらえるはずです。
どうすれば楽に書けるのか
この問題を解決する為に DSL をコードから自動生成してくれるライブラリを作りました。 AstSpecBuilder と名付けたこのライブラリはここで公開しています。使い方はとても簡単、build メソッドに馴染みのあるコードを文字列で渡すだけです:
import astspecbuilder.* def spec = new AstSpecBuilder().build('"Hi, $name."') def expectedSpec = '''\ block { returnStatement { gString 'Hi, $name.', { strings { constant 'Hi, ' constant '.' } values { variable 'name' } } } } ''' assert expectedSpec == specAST からも生成できます。というより実は上のメソッドは次のコードのショートカットに過ぎません:
import astspecbuilder.* def ast = new AstBuilder.buildFromString('"Hi, $name."') def spec = new AstSpecBuilder().build(ast)
インデントはデフォルトではスペース4つですが、変更するオプションが付いてます。次のようにすると DSL がタブ1つでインデントされます:
def spec = new AstSpecBuilder(indent: '\t').build('"foo"') def expectedSpec = '''\ block { \treturnStatement { \t\tconstant 'foo' \t} } ''' assert expectedSpec == spec
No comments:
Post a Comment