AST抽象文法ツリー


原文を読む
AST抽象文法ツリーの紹介
AST(Abstract Syntax Tree)はソースコードの抽象的な文法構造ツリー表現形式であり、Webpack、ESLint、JSX、Type Scriptのコンパイルとモジュール化ルールの転換はASTによってコードの検査、分析、コンパイルなどの操作を実現します.
JavaScript文法のAST文法ツリー
JavaScriptではASTを使って開発したいですが、抽象成語法樹のその後の構造は何ですか?中のフィールド名はどんな意味と遍歴のルールを表していますか?http://esprima.org/demo/parse... JavaScript文法のオンライン変換を実現します.
オンラインコンパイルツールにより、function fn(a, b) {}を以下の構成にコンパイルすることができます.
{
    "type": "Program",
    "body": [
        {
            "type": "FunctionDeclaration",
            "id": {
                "type": "Identifier",
                "name": "fn"
            },
            "params": [
                {
                    "type": "Identifier",
                    "name": "a"
                },
                {
                    "type": "Identifier",
                    "name": "b"
                }
            ],
            "body": {
                "type": "BlockStatement",
                "body": []
            },
            "generator": false,
            "expression": false,
            "async": false
        }
    ],
    "sourceType": "script"
}
JavaScript文法を抽象的な文法ツリーにコンパイルした後、それを遍歴し、修繕し、再コンパイルする必要があります.木の構造を遍歴する過程は「先序深さ優先」です.
esprim、estraverseとescodegenesprimaestraverseescodegenモジュールは、ASTを動作させる3つの重要なモジュールであり、babelのコア依存性を実現するものであり、以下に三つのモジュールの役割を説明する.
1、サプライズはJSをASTに変換します.
esprimモジュールの使い方は以下の通りです.
//   :esprima-test.js
const esprima = require("esprima");

let code = "function fn() {}";

//      
let tree = esprima.parseScript(code);

console.log(tree);

// Script {
//   type: 'Program',
//   body:
//    [ FunctionDeclaration {
//        type: 'FunctionDeclaration',
//        id: [Identifier],
//        params: [],
//        body: [BlockStatement],
//        generator: false,
//        expression: false,
//        async: false } ],
//   sourceType: 'script' }
上記の例から分かるように、JSコードブロックはesprimaモジュールのparseScript方法で成語ツリーに変換され、コードブロックは文字列に変換される必要があり、parseModule方法でモジュールを変換することもできる.
2、estraverse遍歴とAST修正
巡回中を表示:
//   :estraverse-test.js
const esprima = require("esprima");
const estraverse = require("estraverse");

let code = "function fn() {}";

//      
estraverse.traverse(esprima.parseScript(code), {
    enter(node) {
        console.log("enter", node.type);
    },
    leave() {
        console.log("leave", node.type);
    }
});

// enter Program
// enter FunctionDeclaration
// enter Identifier
// leave Identifier
// enter BlockStatement
// leave BlockStatement
// leave FunctionDeclaration
// leave Program
上記のコードはestraverseモジュールのtraverse方法によってesprimaモジュールに変換されたASTを巡回し、type属性のすべてを印刷し、type属性を含むオブジェクトをノードと呼び、対応するタイプを取得し、その時点の属性を変更すれば良い.
実際には、ASTを巡回するということは、各レイヤのtype属性を遍歴することで、2つの段階に分けられ、段階に入ると段階を離れることになり、estraversetraverseの方法でそれぞれパラメータで指定されたentryleaveの2つの関数で傍受されるが、私たちは一般的にはentryを使用するだけである.
3、escodegenはASTをJSに変換する
次の例は、JSコードブロックがASTに変換され、遍歴、修正されたASTをJSに再変換する全プロセスである.
//   :escodegen-test.js
const esprima = require("esprima");
const estraverse = require("estraverse");
const escodegen = require("escodegen");

let code = "function fn() {}";

//      
let tree = esprima.parseScript(code);

//      
estraverse.traverse(tree, {
    enter(node) {
        //      
        if (node.type === "FunctionDeclaration") {
            node.id.name = "ast";
        }
    }
});

//      
let result = escodegen.generate(tree);

console.log(result);

// function ast() {
// }
ASTを巡回する過程でparams値は配列であり、type属性はない.
Babel文法変換プラグインを実現します.
構文変換プラグインを実装するには、babel-coreおよびbabel-typesの2つのモジュールを借りる必要がありますが、実は、これらの2つのモジュールは、esprimaestraverseescodegen 142に依存しています.
この二つのモジュールを使うには、下記のようにインストールが必要です.
npm install babel-core babel-types
1、plugin-tranform-arrow-functionsplugin-transform-arrow-functionsは、矢印関数をES 5文法に変換するためのBabel家族の一員である.
//   :plugin-transform-arrow-functions.js
const babel = require("babel-core");
const types = require("babel-types");

//        
let sumCode = `
const sum = (a, b) => {
    return a + b;
}`;
let minusCode = `const minus = (a, b) => a - b;`;

//    ES5   
let ArrowPlugin = {
    //    (     )
    visitor: {
        // path      
        ArrowFunctionExpression(path) {
            //      
            let node = path.node;

            //         
            let params = node.params;
            let body = node.body;

            //            ,         return   {}
            if (!types.isBlockStatement(body)) {
                let returnStatement = types.returnStatement(body);
                body = types.blockStatement([returnStatement]);
            }

            //             
            let func = types.functionExpression(null, params, body, false, false);

            //               
            types.replaceWith(func);
        }
    }
};

//          
let sumResult = babel.transform(sumCode, {
    plugins: [ArrowPlugin]
});

let minusResult = babel.transform(minusCode, {
    plugins: [ArrowPlugin]
});

console.log(sumResult.code);
console.log(minusResult.code);

// let sum = function (a, b) {
//   return a + b;
// };
// let minus = function (a, b) {
//   return a - b;
// };
私たちは主にbabel-coretransform方法を用いてASTをコードブロックに変換し、最初のパラメータは変換前のコードブロック(文字列)であり、第二のパラメータは構成項目であり、plugins値は配列であり、babal-core変換されたASTを修正するプラグイン(オブジェクト)を記憶し、transform方法を用いて古いASTを新しいコードブロックに処理した後、戻り値はオブジェクトで、オブジェクトのcode属性は変換されたコードブロック(文字列)です.
内部修正は、babel-typesモジュールによって提供される方法によって実現され、APIは、APIに到達することができる.https://github.com/babel/babe... に表示されますArrowPluginは、transform方法に導入されたプラグインであり、visitor属性(固定)を含んでいなければならない.値は同じオブジェクトであり、構文ツリーを修正する方法を記憶するために使用され、方法名は、APIに厳格に従い、対応する方法は、AST対応するノードを修正する.types.functionExpression方法では、パラメータはそれぞれ表しています.関数名(匿名関数null)、関数パラメータ(必須)、関数体(必須)、generator関数(デフォルトfalse)かどうか、async関数(デフォルトfalse)かどうか、戻り値は修正されたAST、types.replaceWith方法はASTを置換するために使用されます.
2、plugin-tranform-clasesplugin-transform-classesはまた、Babel家族の一員であり、ES 6のclassクラスをES 5に変換するためのコンストラクタでもある.
//   :plugin-transform-classes.js
const babel = require("babel-core");
const types = require("babel-types");

//  
let code = `
class Person {
    constructor(name) {
        this.name = name;
    }
    getName () {
        return this.name;
    }
}`;

//      ES5       
let ClassPlugin = {
    visitor: {
        ClassDeclaration(path) {
            let node = path.node;
            let classList = node.body.body;

            //              { type: 'Identifier', name: 'Person' }
            let className = types.identifier(node.id.name);
            let body = types.blockStatement([]);
            let func = types.functionDeclaration(className, [], body, false, false);
            path.replaceWith(func);

            //           
            let es5Func = [];

            //    class      
            classList.forEach((item, index) => {
                //       
                let body = classList[index].body;

                //     
                let params = item.params.length ? item.params.map(val => val.name) : [];

                //         
                params = types.identifier(params);

                //       constructor,                
                if (item.kind === "constructor") {
                    //            
                    func = types.functionDeclaration(className, [params], body, false, false);
                } else {
                    //          
                    let proto = types.memberExpression(className, types.identifier("prototype"));

                    //           Person.prototype.getName
                    let left = types.memberExpression(proto, types.identifier(item.key.name));

                    //         
                    let right = types.functionExpression(null, [params], body, false, false);

                    //                
                    es5Func.push(types.assignmentExpression("=", left, right));
                }
            });

            //         ,    
            if (es5Func.length === 0) {
                path.replaceWith(func);
            } else {
                es5Func.push(func);
                //    n    
                path.replaceWithMultiple(es5Func);
            }
        }
    }
};

//          
result = babel.transform(code, {
    plugins: [ClassPlugin]
});

console.log(result.code);

// Person.prototype.getName = function () {
//     return this.name;
// }
// function Person(name) {
//     this.name = name;
// }
上記のプラグインの実装は、plugin-transform-arrow-functionsより複雑であり、結局は相互に変換されるES 6とES 5シンタックスツリーを比較して、彼らの違いを見つけ、babel-typesで提供されるAPIを使用して、シンタックスツリーに対応するノード属性を修正して、シンタックスツリーを置換します.配列は、複数の構文ツリー構造をサポートしており、構文ツリーを具体的に変更するシーンに応じて選択して使用することができ、場合によっては異なる代替方法を使用することもできる.
締め括りをつける
このセクションを通じて、AST抽象的な文法ツリー、抽象的な文法ツリーとJavaScriptの中の表現、およびNodeJSの中でAST抽象的な文法ツリーの生成、遍歴および修正のためのコア依存性を理解し、path.replaceWithMultiplepath.replaceWithの2つのモジュールを使って、ES 6の新しい特殊性をES 5文法に変換するプロセスを簡易的に模擬しました.後で自分でいくつかのコンパイルのプラグインを実現するために構想を提供することができることを望みます.