抽象的な文法の木のJavaScriptの中の応用

11727 ワード

抽象文法の木は何ですか?
コンピュータ科学では、抽象的な文法ツリー(abstract sysntax treeまたはASTと略される)または文法ツリー(syntax tree)は、ソースコードの抽象的な文法構造のツリー表現形式であり、ここではプログラミング言語のソースコードを指す.ツリー上の各ノードはソースコードの構造を表している.文法が「抽象的」というのは、ここの文法は実際の文法に出てくる細部を表していないからです.1
やはり抽象的なほうがいいです.いくつかの例を見てみましょう.
抽象文法ツリーの例
foo = 'hello world';
/*
    +-------------+             
    |  assign(=)  |             
    +-------------+             
       X        X               
      X          X              
+-------+    +-----------------+
|  foo  |    |  'hello world'  |
+-------+    +-----------------+
*/
if (foo === true) {
  bar = 'hello world';
  alert(bar);
}
/*
                       +------+                                    
                       |  if  |                                    
                       +------+                                    
                        X    X                                     
                      X        X                                   
         +--------------+    +-------------+                       
         |  equal(===)  |    |  if_body    |                       
         +--------------+    +-------------+                       
         X        X              X         X                       
       X         X                X          X                     
+-------+   +--------+    +-------------+   +------------+         
|  foo  |   |  true  |    |  assign(=)  |   |  alert()   |         
+-------+   +--------+    +-------------+   +------------+         
                             X        X                  X         
                           X            X                  X       
                       +-------+   +-----------------+    +-------+
                       |  bar  |   |  'hello world'  |    |  bar  |
                       +-------+   +-----------------+    +-------+
*/
上記の2つの例から、抽象的な構文ツリーはソースコードをその構文構造に従って、いくつかの詳細(例えば、括弧がノードを生成していない)を省略し、ツリー表現に抽象化することが分かる.
抽象的な文法の木はコンピュータ科学に多くの応用があります.たとえば、コンパイラ、IDE、圧縮最適化コードなどです.抽象的な文法の木のJavaScriptでの応用を紹介します.
JavaScript抽象文法ツリー
JavaScriptを構成する抽象的な文法ツリーには、v 8、Spider Monkey、UglifyJSなどのツールがあります.ここではUglifyJSを紹介します.
UglifyJS
UglifyJSは最も広く使われているJavaScript圧縮ツールの一つで、しかも自身もJavaScriptで書いています.その方法を使うのは簡単です.
まずグローバルインストール:
[sudo ]npm install -g uglify-js
そして使用できます.
uglifyjs -m srcFileName.js -o destFileName.min.js
UglifyJSの使い方についてはここであまり紹介しません.もっと面白いことをしたいです.
UglifyJS Tools
UglifyJSはJavaScriptコードを分析するためのツールを提供しています.
  • パーパーパー、JavaScriptコードを抽象文法ツリー
  • に解析します.
  • code generatorは、抽象文法ツリーからコード
  • を生成する.
  • magler、JavaScriptコードを混同する
  • scope anlyzer、変数定義を解析するツール
  • tree walkerは、ツリーノード
  • を巡回します.
  • tree transformer、ツリーノード
  • を変更します.
    抽象文法ツリーを生成
    UglifyJSを使って抽象的な文法ツリーを生成するのは簡単です.
    まずUglifyJSをインストールします.npmパッケージです.
    npm install uglify-js --save-dev
    
    その後パース方法を使えばいいです.
    var UglifyJS = require('uglify-js');
    
    var ast = UglifyJS.parse('function sum(foo, bar){ return foo + bar; }');
    
    このように生成されたastは、そのセグメントコードの抽象的な文法ツリーである.じゃ私達はどう使いますか?
    maglerを使ってコードを圧縮します.
    maglerを使用すると、ローカル変数を一つの文字に短縮することでコードを圧縮することができます.
    var UglifyJS = require('uglify-js');
    
    var ast = UglifyJS.parse('function sum(foo, bar){ return foo + bar; }');
    ast.figure_out_scope();
    ast.mangle_names();
    console.log(ast.print_to_string());
    // function sum(a,b){return a+b}
    
    ウォーカーを使って抽象的な文法の木を遍歴します.
    ウォーカーを使って抽象的な文法の木を遍歴することができます.この遍歴は深いです.
    var UglifyJS = require('uglify-js');
    
    var ast = UglifyJS.parse('function sum(foo, bar){ return foo + bar; }');
    ast.figure_out_scope();
    ast.walk(new UglifyJS.TreeWalker(function(node) {
        console.log(node.print_to_string());
    }));
    /*
    function sum(foo,bar){return foo+bar}
    function sum(foo,bar){return foo+bar}
    sum
    foo
    bar
    return foo+bar
    foo+bar
    foo
    bar
    */
    
    UglifyJSはコードを直接圧縮するスクリプトを提供しました.walkerは見たところ役に立たないようですが、これらのツールはどのような使用場面がありますか?
    抽象文法ツリーの応用
    抽象的な文法の木を利用してJavaScriptコードを再構築します.
    JavaScriptを再構築する需要があれば、役に立ちます.
    次はこのような需要を考慮します.parseIntは文字列を整数にするために使用されることを知っていますが、第二のパラメータがあります.文字列を何進で識別するかを表しています.
    parseInt('10.23');     // 10                  
    parseInt('10abc');     // 10                  
    parseInt('10', 10);    // 10                  
    parseInt('10', 2);     // 2                   
    parseInt('0123');      // 83 or 123             ,             
    parseInt('0x11');      // 17                   
    
    私たちが予想していたのとは異なる場合がありますので、いつでも第二のパラメータを加えることをおすすめします.
    以下はシナリオがあることを望んで、すべてのparseIntが第2のパラメーターがあるかどうかを調べて、ないなら第2のパラメーター10を加えて、10進数で文字列を識別することを表します.
    UglifyJSを使ってこの機能を実現できます.
    #! /usr/bin/env node
    
    var U2 = require("uglify-js");
    
    function replace_parseint(code) {
        var ast = U2.parse(code);
        // accumulate `parseInt()` nodes in this array
        var parseint_nodes = [];
        ast.walk(new U2.TreeWalker(function(node){
            if (node instanceof U2.AST_Call
                && node.expression.print_to_string() === 'parseInt'
                && node.args.length === 1) {
                parseint_nodes.push(node);
            }
        }));
        // now go through the nodes backwards and replace code
        for (var i = parseint_nodes.length; --i >= 0;) {
            var node = parseint_nodes[i];
            var start_pos = node.start.pos;
            var end_pos = node.end.endpos;
            node.args.push(new U2.AST_Number({
                value: 10
            }));
            var replacement = node.print_to_string({ beautify: true });
            code = splice_string(code, start_pos, end_pos, replacement);
        }
        return code;
    }
    
    function splice_string(str, begin, end, replacement) {
        return str.substr(0, begin) + replacement + str.substr(end);
    }
    
    // test it
    
    function test() {
        if (foo) {
          parseInt('12342');
        }
        parseInt('0012', 3);
    }
    
    console.log(replace_parseint(test.toString()));
    
    /*
    function test() {
        if (foo) {
          parseInt("12342", 10);
        }
        parseInt('0012', 3);
    }
    */
    
    ここで、ウォーカーを使ってparseIntが呼び出したところを見つけ、第二のパラメータがあるかどうかを確認し、なければ記録し、その後、各記録に基づいて、新しい第二のパラメータを含む内容で元の内容を入れ替えて、コードの再構成を完了する.
    このような簡単な状況は正則で合わせても便利に変えられます.なぜ抽象的な文法の木を使うのですか?
    答えは、抽象的な文法ツリーは、文法を分析することによって実現され、いくつかの正則ではできない(または難しい)利点があります.たとえば、パーrseInt()は全体が文字列であり、または注釈では、このような状況は正則によって誤って判定されます.
    var foo = 'parseInt("12345")';
    // parseInt("12345");
    
    抽象的な文法の木はアメリカ団の中の応用です.
    チームのフロントエンドでは、YUIをフロントエンドの下のフレームワークとして使用していますが、これまで直面していた実際の問題は、モジュール間の依存関係がミスしやすいことです.たとえば:
    YUI.add('mod1', function(Y) {
        Y.one('#button1').simulate('click');
        Y.Array.each(array, fn);
        Y.mod1 = function() {/**/};
    }, '', {
        requires: [
            'node',
            'array-extras'
        ]
    });
    YUI.add('mod2', function(Y) {
        Y.mod1();
        // Y.io(uri, config);
    }, '', {
        requires: [
            'mod1',
            'io'
        ]
    });
    
    以上のコードは2つのモジュールを定義しており、mod1idの要素をクリックしてbutton1を実行した後、方法Y.Array.eachを定義し、最後に依存性Y.mod1nodeを宣言した.array-extrasmod2で定義された方法を実行し、mod1は注釈され、最後に依存性Y.ioおよびmod1を宣言した.
    ここでioは、2つのよくあるエラーが発生しています.1つはmod1上の方法で、文依存性を忘れがちです.もう1つはsimulate上の方法の一部のみがY.Node.prototypeに依存する必要があるので、ここで多くの文が示されました.node-event-simulateに注釈を追加すると、元の書き込みの依存性を削除することを忘れがちである.
    したがって、正確な依存関係は以下の通りであるべきである.
    YUI.add('mod1', function(Y) {
        Y.one('#button1').simulate('click');
        Y.Array.each(array, fn);
        Y.mod1 = function() {/**/};
    }, '', {
        requires: [
            'node',
            'node-event-simulate'
        ]
    });
    YUI.add('mod2', function(Y) {
        Y.mod1();
        // Y.io(uri, config);
    }, '', {
        requires: [
            'mod1'
        ]
    });
    
    モジュール依存性の検出を自動化するために、モジュール依存関係の検出ツールを作成しました.抽象的な文法ツリーを利用して、どのインターフェースを定義し、どのインターフェースを使用しているかを分析しました.そして、これらのインターフェースがどのモジュールに依存すべきかを調べて、モジュール依存関係のエラーを見つけました.大体のプロセスは以下の通りです.
  • コード中のモジュール定義(Y.Array)の部分
  • を見つけました.
  • は、各モジュール内の関数定義、変数定義、割当語句などを分析し、要求(array-extrasで始まる)に合致する出力インターフェース(array-extrasmod2)
  • を探し出す.
  • 「インターフェース・モジュール」対応関係を生成する
  • .
  • は、各モジュール内の関数呼び出し、変数の使用などを分析し、要求に合致する入力インターフェース(ioYUI.addYmod1)
  • を探し出す.
  • 「インターフェース・モジュール」の対応関係を通じて、このモジュールはどの他のモジュール
  • に依存すべきかを見つける.
  • 、requiresにエラーがあるかどうかを分析する
  • .
    このツールを使って、コードを提出するたびに依存関係が正しいことを保証します.モジュール依存関係の検出の自動化を実現しました.
    締め括りをつける
    抽象的な文法の木はコンピュータの領域の中で応用が広くて、以上は抽象的な文法の木のJavaScriptの中のいくつかの応用だけを討論しました.
    Reference
  • Wikipedia AST
  • UglifyJS
  • node-event-simulte
  • Y.Aray.each
  • 文章に誤りがあり、内容に疑問があることを発見したら、技術チームのWeChat公式アカウントに注目し、バックグラウンドで私達にメッセージを送ることができます.私達は週に1人の熱心な子供を選んで、1部の精巧で美しい贈り物を送ります.早く掃除に来てください.私たちに注目してください.