Skip to content

Instantly share code, notes, and snippets.

@azu
Created August 8, 2015 11:08
Show Gist options
  • Save azu/a6fdaec88c464c976402 to your computer and use it in GitHub Desktop.
Save azu/a6fdaec88c464c976402 to your computer and use it in GitHub Desktop.
AST変換関数とツール

AST変換関数とツール

AST変換関数を作るのは簡単。 だが、実際に変換する際の入力はコードであり、コードを入力とする場合にパーサが必要となり、 最終的な出力はコードであるため、ジェネレータが必要となる。

parse(コード) -> AST -> AST変換関数(AST) -> AST -> generate(AST) -> コード

AST変換関数を使ったものは、基本的にはコードを入力としコードを出力とする流れがあり、 それぞれのAST変換関数ごとにこれを提供するのは大変となる。

なので、

parse(コード) -> AST -> AST変換関数(AST) -> AST -> generate(AST) -> コード

太字の部分を提供出来るようにするのが目的。

つまりAST変換関数を補助するツール。

類似プロジェクトとして、asterjs/asterが存在し、asterはGruntやGulpのようにビルドツールとして動いてる。

ビルドツールである場合に、ユーザがそのビルドツールに依存しなければならないため、求めるものとは少し異なる。

"コードを入力としてコードを出力するものを簡単に作れる" という事ができれば目的は達成できるのではないかと思う。

ただ、最近はesprima、espree、acorn、Babylonなど色々なパーサが存在するため、 ESTreeレベル(現在だとES6レベル)だと互換性はあるが、ES.nextやJSXといた拡張に対する扱い、ASTではなくCSTレベルの互換性は存在していない。

そのため、コード -> ASTへと変換した際に、そのAST変換関数が求めるASTであるかどうか、 厳密にはCSTに依存したものであるかという条件を知り得ないと"コードを入力としてコードを出力するものを簡単に作れる"とならないAST変換関数が存在する。

AST変換関数

  • コメントが入ったASTが欲しい
  • token(CST)が入ったものが欲しい
  • ES.nextに対応したASTで欲しい
    • これは下位互換があるので、AST変換関数はバージョンは気にしなくて良いはず
    • ASTとしてvalidであるならばAST変換関数はASTにおいてエラーはほぼない(変換できないだけ)

つまり?

  • パーサ的にあるオプションが設定されていて欲しい
  • このパーサを使って欲しい

という条件が設定されたパーサでパースされた結果をASTが求めるものであるとかんがえる。

複数のAST変換関数の組み合わせ

AST変換関数同士を組み合わせるときに考える問題

  • 変換の順番
  • 正常なASTであること
  • 変換中にそのAST変換関数が求めるASTとなっているかどうか

変換の順番はユーザ/AST変換関数を組み合わせる開発者が決めるべき問題である。

そのため複数のAST変換関数を組み合わせというものは、以下のように変換毎にvalidateを行えば大体解決できるはず。

validateAST(bTransform(validateAST(aTransform(AST))))

AST変換関数を補助するツール

AST変換関数が直接パーサを指定すると何が問題なのかというと、 ユーザーが与えたコードをパースできないという問題が起きる可能性があるためである。

例えば、JSXを使ったコードをesprimaに渡すとパースエラーとなり、そのAST変換関数はそのコードを変換できないという事になってしまう。

何でもパースできるだろうという過程でBabylonを使えばいいというわけでもなく、先ほど書いたようにCSTにおける互換性はないため、babel/babel-eslintのようにtokenに関する変換が必要になるかもしれない。

しかし、結局受け入れるのユーザが書いたコードであるためそれがパースできれば問題ないと思える。

つまり、AST変換関数を補助するツールに求められるものは、

  • ユーザのコードをパースできること
  • AST変換関数が必要としてる情報がASTに含まれてる事を保証すること

現状ではESTreeというデファクトが存在し、そのコードがESTreeで扱えないものであるならば、何らかのTranspileが必要となるはずである。

つまり、そのプロジェクトにはTranspileの依存が含まれているとかんがえる

  • babelがあるなら -> babelをパーサとして使う
  • それ以外?
  • それ以外であるならばesprimaでパースする

つまりBabelはpeerDependenciesに落とすことができるはず。

parseOptions

var AST = parse(code, options);
var modifiedAST = transform(AST);
generate(AST)

=> parse

var output = new Source(code, options)
.transform(transform)
.output();
var code = output.code;
var sourcemap = output.map;
// == equel
var source = new Source(options);
source.input(code);
source.transform(transform);
source.output();
@azu
Copy link
Author

azu commented Aug 9, 2015

var source = new Source(ast, options);
source.transform(transform);
source.strictTransform(transform);
source.output();

@azu
Copy link
Author

azu commented Aug 9, 2015

として実装してある

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment