UglifyJSのパーサを試してみる

前回の記事の続き。
asyncblockのソースを調べたところ、asyncblockのsource transformationは、内部でUglifyJSを使っていた。
UglifyJSは普通はソースをminifyする用途で使われるが、その一部としてjsの文をparseしており(当然か)、その部分だけ抜き出して使うこともできる。
ということで今回は、var foo = 12 + 34 * 56; という単純な文をパースしてみて、どのようになるか試してみる。

準備

npm install uglify-js

スクリプト

var parse = require('uglify-js').parse;
var code = 'var foo = 12 + 34 * 56;'
var result = parse(code);
// ネストが深かったので、今回はresult.bodyだけを出力。(配列だったけど中身1つだけだったので、body[0]を出力した)
console.log(JSON.stringify(result.body[0]));

結果(整形後)

結果を整形したところ、ものすごい長かったです。
何行目の何文字目等、直接文法に関係しないところを全部とっぱらった結果が以下

{
    // var
    "start": {
        "type": "keyword", 
        "value": "var"
    },

    // ;
    "end": {
        "type": "punc", 
        "value": ";"
    },

    // startが"var"の時は、その中身は"definitions"になるということだろう。
    // 代入文
    "definitions": [
        // definitionsの中身はname(変数名)とvalue(代入する値)

        // foo: 12 * 34 + 56
        {
            "start": {
                "type": "name", 
                "value": "foo"
            }, 
            "end": {
                "type": "num", 
                "value": 56
            }, 
            // 代入される変数名 foo
            "name": {
                "start": {
                    "type": "name", 
                    "value": "foo"
                }
                "end": {
                    "type": "name", 
                    "value": "foo"
                }, 
                "name": "foo", 
            },
            // 代入する値。値といってもまだ子がついてる部分木
            // 12 * 34 + 56
            "value": {
                "start": {
                    "type": "num", 
                    "value": 12
                }
                "end": {
                    "type": "num", 
                    "value": 56
                },
                // + 
                "operator": "+", 
                // +演算子の左がわ。12
                "left": {
                    // 最も小さい単位のトークンは、start・end・(value / name など?)が全て同じように定義される模様
                    "start": {
                        "type": "num", 
                        "value": 12
                    }, 
                    "end": {
                        "type": "num", 
                        "value": 12
                    }, 
                    "value": 12
                }, 
                
                // +演算子の右側。34 * 56
                "right": {
                    // ここも最も小さいトークン。それ以降はなし
                    "start": {
                        "type": "num", 
                        "value": 34
                    },
                    "end": {
                        "type": "num", 
                        "value": 56
                    }, 
                    "operator": "*", 
                    // *演算子の左側。34
                    "left": {
                        "start": {
                            "type": "num", 
                            "value": 34
                        }, 
                        "end": {
                            "type": "num", 
                            "value": 34
                        }, 
                        "value": 34
                    }, 
                    // *演算子の右側。56
                    "right": {
                        "end": {
                            "type": "num", 
                            "value": 56
                        }, 
                        "start": {
                            "type": "num", 
                            "value": 56
                        }, 
                        "value": 56
                    }, 
                }, 
            }
        }
    ], 
}

まとめ

構文解析木内のどのノードにも、start, end, 中身を表すキーがある。(葉ノードの時とかはstart, end, value全て同じようなデータになってるみたい。)
中身を表すキーは、

  • var宣言文… definitions
  • 変数への代入… name(変数名), value(代入する中身。今回は数式)
  • 数式… oprator(+とか*), left(演算子の左)、right(演算子の右)
  • 数値などの値… value

次はこれをどう操作しているのかを見ていこうと思います。…が、asyncblockはuglify-jsを自分でカスタマイズしたのを使っているようなので、その辺ちゃんと解読できるか心配。