精华内容
下载资源
问答
  • 抽象语法树怎么画
    2021-05-21 13:59:29

    AST抽象语法树

    why主流项目插件的用途: javascript转译、代码压缩、css预处理、eslint、prettier等都建立在AST的基础上。

    whataccording to the grammar of a programming language, each AST node corresponds to an item of a source code.(根据编程语言的语法,每个AST节点对应一个源代码项。)

    demo

    链接地址:astexplorer.net

    AST解析工具

    bVcKV2X

    js语法function square(n) {

    return n * n;

    }

    ast语法树// Parser acorn-8.0.1

    {

    "type": "Program",

    "start": 0,

    "end": 38,

    "body": [

    {

    "type": "FunctionDeclaration",

    "start": 0,

    "end": 38,

    "id": {

    "type": "Identifier",

    "start": 9,

    "end": 15,

    "name": "square"

    },

    "expression": false,

    "generator": false,

    "async": false,

    "params": [

    {

    "type": "Identifier",

    "start": 16,

    "end": 17,

    "name": "n"

    }

    ],

    "body": {

    "type": "BlockStatement",

    "start": 19,

    "end": 38,

    "body": [

    {

    "type": "ReturnStatement",

    "start": 23,

    "end": 36,

    "argument": {

    "type": "BinaryExpression",

    "start": 30,

    "end": 35,

    "left": {

    "type": "Identifier",

    "start": 30,

    "end": 31,

    "name": "n"

    },

    "operator": "*",

    "right": {

    "type": "Identifier",

    "start": 34,

    "end": 35,

    "name": "n"

    }

    }

    }

    ]

    }

    }

    ],

    "sourceType": "module"

    }

    从纯文本中得到AST(通过编译器)词法分析

    scanner。它读取我们的代码,然后把他们按照预定的规则合并成一个个的标识(tokens).同时,它会移除空白符,注释等。最后,整个代码将被分割进一个tokens列表(或者说一维数组)。当词法分析源代码的时候,它会一个一个字母的读取代码。当它遇到空格,操作符,或者特殊符号的时候,它会认为一个会话已经完成了。

    语法解析,也叫解析器

    它将词法分析出来的数组转化成树形的表达形式。同时验证语法,语法错误,抛出语法错误。

    当生成树的时候,解析器会删除一些没必要的标识tokens(比如不完整的括号),因此AST不是100%与源码匹配,但我们已经能够知道如何处理了。题外话,解析器100%覆盖所有代码结构生成树叫做CST(具体语法树)

    更多编译器知识将Lisp转化为C语言创造自己的语言,并将它编译成C语言或者机器语言,最后运行它。

    重点介绍Babylon

    Babylon

    Babylon is a JavaScript parser used in Babel.Support for JSX, Flow, Typescript.

    babel

    babel是一个javascript编译器。宏观来说,它分为3个阶段运行代码:解析(parsing),转译(transforming),生成(generation)。我们可以给babel一些javascript代码,它修改代码然后生成新的代码返回。过程即创建AST,遍历树,修改tokens,最后从AST中生成最新的代码。

    babel解析生成

    1、使用babylon解析代码生成语法树import * as babylon from "babylon";

    const code = `

    const abc = 5;

    `;

    const ast = babylon.parse(code);

    生成树结果:{

    "type": "File",

    "start": 0,

    "end": 18,

    "loc": {

    "start": {

    "line": 1,

    "column": 0

    },

    "end": {

    "line": 3,

    "column": 0

    }

    },

    "program": {

    "type": "Program",

    "start": 0,

    "end": 18,

    "loc": {

    "start": {

    "line": 1,

    "column": 0

    },

    "end": {

    "line": 3,

    "column": 0

    }

    },

    "sourceType": "script",

    "body": [

    {

    "type": "VariableDeclaration",

    "start": 3,

    "end": 17,

    "loc": {

    "start": {

    "line": 2,

    "column": 2

    },

    "end": {

    "line": 2,

    "column": 16

    }

    },

    "declarations": [

    {

    "type": "VariableDeclarator",

    "start": 9,

    "end": 16,

    "loc": {

    "start": {

    "line": 2,

    "column": 8

    },

    "end": {

    "line": 2,

    "column": 15

    }

    },

    "id": {

    "type": "Identifier",

    "start": 9,

    "end": 12,

    "loc": {

    "start": {

    "line": 2,

    "column": 8

    },

    "end": {

    "line": 2,

    "column": 11

    },

    "identifierName": "abc"

    },

    "name": "abc"

    },

    "init": {

    "type": "NumericLiteral",

    "start": 15,

    "end": 16,

    "loc": {

    "start": {

    "line": 2,

    "column": 14

    },

    "end": {

    "line": 2,

    "column": 15

    }

    },

    "extra": {

    "rawValue": 5,

    "raw": "5"

    },

    "value": 5

    }

    }

    ],

    "kind": "const"

    }

    ],

    "directives": []

    },

    "comments": [],

    "tokens": [

    {

    "type": {

    "label": "const",

    "keyword": "const",

    "beforeExpr": false,

    "startsExpr": false,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "value": "const",

    "start": 3,

    "end": 8,

    "loc": {

    "start": {

    "line": 2,

    "column": 2

    },

    "end": {

    "line": 2,

    "column": 7

    }

    }

    },

    {

    "type": {

    "label": "name",

    "beforeExpr": false,

    "startsExpr": true,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null

    },

    "value": "abc",

    "start": 9,

    "end": 12,

    "loc": {

    "start": {

    "line": 2,

    "column": 8

    },

    "end": {

    "line": 2,

    "column": 11

    }

    }

    },

    {

    "type": {

    "label": "=",

    "beforeExpr": true,

    "startsExpr": false,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": true,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "value": "=",

    "start": 13,

    "end": 14,

    "loc": {

    "start": {

    "line": 2,

    "column": 12

    },

    "end": {

    "line": 2,

    "column": 13

    }

    }

    },

    {

    "type": {

    "label": "num",

    "beforeExpr": false,

    "startsExpr": true,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "value": 5,

    "start": 15,

    "end": 16,

    "loc": {

    "start": {

    "line": 2,

    "column": 14

    },

    "end": {

    "line": 2,

    "column": 15

    }

    }

    },

    {

    "type": {

    "label": ";",

    "beforeExpr": true,

    "startsExpr": false,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "start": 16,

    "end": 17,

    "loc": {

    "start": {

    "line": 2,

    "column": 15

    },

    "end": {

    "line": 2,

    "column": 16

    }

    }

    },

    {

    "type": {

    "label": "eof",

    "beforeExpr": false,

    "startsExpr": false,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "start": 18,

    "end": 18,

    "loc": {

    "start": {

    "line": 3,

    "column": 0

    },

    "end": {

    "line": 3,

    "column": 0

    }

    }

    }

    ]

    }

    2、使用babel的转换器transforming语法树语法import traverse from "babel-traverse";

    traverse(ast, {

    enter(path) {

    if (path.node.type === "Identifier") {

    path.node.name = path.node.name

    .split("")

    .reverse()

    .join("");

    }

    }

    });{

    "type": "File",

    "start": 0,

    "end": 18,

    "loc": {

    "start": {

    "line": 1,

    "column": 0

    },

    "end": {

    "line": 3,

    "column": 0

    }

    },

    "program": {

    "type": "Program",

    "start": 0,

    "end": 18,

    "loc": {

    "start": {

    "line": 1,

    "column": 0

    },

    "end": {

    "line": 3,

    "column": 0

    }

    },

    "sourceType": "script",

    "body": [

    {

    "type": "VariableDeclaration",

    "start": 3,

    "end": 17,

    "loc": {

    "start": {

    "line": 2,

    "column": 2

    },

    "end": {

    "line": 2,

    "column": 16

    }

    },

    "declarations": [

    {

    "type": "VariableDeclarator",

    "start": 9,

    "end": 16,

    "loc": {

    "start": {

    "line": 2,

    "column": 8

    },

    "end": {

    "line": 2,

    "column": 15

    }

    },

    "id": {

    "type": "Identifier",

    "start": 9,

    "end": 12,

    "loc": {

    "start": {

    "line": 2,

    "column": 8

    },

    "end": {

    "line": 2,

    "column": 11

    },

    "identifierName": "abc"

    },

    "name": "cba"

    },

    "init": {

    "type": "NumericLiteral",

    "start": 15,

    "end": 16,

    "loc": {

    "start": {

    "line": 2,

    "column": 14

    },

    "end": {

    "line": 2,

    "column": 15

    }

    },

    "extra": {

    "rawValue": 5,

    "raw": "5"

    },

    "value": 5

    }

    }

    ],

    "kind": "const"

    }

    ],

    "directives": []

    },

    "comments": [],

    "tokens": [

    {

    "type": {

    "label": "const",

    "keyword": "const",

    "beforeExpr": false,

    "startsExpr": false,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "value": "const",

    "start": 3,

    "end": 8,

    "loc": {

    "start": {

    "line": 2,

    "column": 2

    },

    "end": {

    "line": 2,

    "column": 7

    }

    }

    },

    {

    "type": {

    "label": "name",

    "beforeExpr": false,

    "startsExpr": true,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null

    },

    "value": "abc",

    "start": 9,

    "end": 12,

    "loc": {

    "start": {

    "line": 2,

    "column": 8

    },

    "end": {

    "line": 2,

    "column": 11

    }

    }

    },

    {

    "type": {

    "label": "=",

    "beforeExpr": true,

    "startsExpr": false,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": true,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "value": "=",

    "start": 13,

    "end": 14,

    "loc": {

    "start": {

    "line": 2,

    "column": 12

    },

    "end": {

    "line": 2,

    "column": 13

    }

    }

    },

    {

    "type": {

    "label": "num",

    "beforeExpr": false,

    "startsExpr": true,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "value": 5,

    "start": 15,

    "end": 16,

    "loc": {

    "start": {

    "line": 2,

    "column": 14

    },

    "end": {

    "line": 2,

    "column": 15

    }

    }

    },

    {

    "type": {

    "label": ";",

    "beforeExpr": true,

    "startsExpr": false,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "start": 16,

    "end": 17,

    "loc": {

    "start": {

    "line": 2,

    "column": 15

    },

    "end": {

    "line": 2,

    "column": 16

    }

    }

    },

    {

    "type": {

    "label": "eof",

    "beforeExpr": false,

    "startsExpr": false,

    "rightAssociative": false,

    "isLoop": false,

    "isAssign": false,

    "prefix": false,

    "postfix": false,

    "binop": null,

    "updateContext": null

    },

    "start": 18,

    "end": 18,

    "loc": {

    "start": {

    "line": 3,

    "column": 0

    },

    "end": {

    "line": 3,

    "column": 0

    }

    }

    }

    ]

    }

    3、使用babel的生成器generator代码import generate from "@babel/generator";

    const newCode = generate(ast).code;

    // newCode => const cba = 5;

    babel插件制作(babel-plugins)

    在上述步骤中,第一步(解析)和第三步(生成)有babel处理。

    当开发babel-plugin插件的时候,我们只需要描述转化你的AST节点的"visitors"就可以了。// my-babel-plugin.js

    module.exports = function() {

    return {

    visitor: {

    Identifier(path) {

    const name = path.node.name;

    console.log(name);

    path.node.name = name

    .split("")

    .reverse()

    .join("");

    }

    }

    };

    };

    // 在babel.config.js中注册插件,重启项目才能生效

    // plugins: ["./src/plugins/mybabelplugin.js"]

    自动代码重构工具,神器JSCodeshift

    例如说你想要替换掉所有的老掉牙的匿名函数, 把他们变成Lambda表达式(箭头函数)。// transform

    load().then(function(response)) {

    return response.data;

    }

    // to

    load().then(response => response.data)

    上述操作代码编辑器可能没办法这么做,因为这并不是简单的查找替换操作。这时候jscodeshift就可以使用了。

    如果你想创建自动把你的代码从旧的框架迁移到新的框架,这就是一种很nice的方式。

    jscodeshift是一个工具包,用于在多个JavaScript或TypeScript文件上运行codemods。

    react-codemod

    This repository contains a collection of codemod scripts for use with JSCodeshift that help update React APIs.

    此存储库包含一组codemod脚本,用于jscodeshift,用于更新React api。

    Prettier// transform

    foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis()),isThereSeriouselyAnotherOne());

    // to

    foo {

    reallyLongArg(),

    omgSoManyParameters(),

    IShouldRefactorThis(),

    isThereSeriouselyAnotherOne()

    };

    // Prettier 格式化我们的代码。它调整长句,整理空格,括号等。

    Finally

    它将js代码转化生成svg流程图

    这是一个很好的例子,因为它向你展现了你,当你拥有AST时,可以做任何你想要做的事。把AST转回成字符串代码并不是必要的,你可以通过它画一个流程图,或者其它你想要的东西。

    js2flowchart使用场景是什么呢?通过流程图,你可以解释你的代码,或者给你代码写文档;通过可视化的解释学习其他人的代码;通过简单的js语法,为每个处理过程简单的描述创建流程图。

    你也可以在代码中使用它,或者通过CLI,你只需要指向你想生成SVG的文件就行。而且,还有VS Code插件(链接在项目readme中)

    首先,解析代码成AST,然后,我们遍历AST并且生成另一颗树,我称之为工作流树。它删除很多不重要的额tokens,但是将关键块放在一起,如函数、循环、条件等。再之后,我们遍历工作流树并且创建形状树。每个形状树的节点包含可视化类型、位置、在树中的连接等信息。最后一步,我们遍历所有的形状,生成对应的SVG,合并所有的SVG到一个文件中.

    后续会持续更新,学习中。。。

    更多相关内容
  • -这时你需要懂得抽象语法树(AST)。 AST在日常业务中也许很难涉及到,但当你不止于想做一个工程师,而想做工程师的工程师,写出类似webpack,vue-cli前端自动化的工具,或者有批量修改源码的工程需求,那你必须...
  • DelphiAST, Delphi抽象语法树生成器 Delphi的抽象语法树生成器使用 DelphiAST,你可以使用真正的Delphi代码并获得抽象语法树。 一个单位,但没有符号表。FreePascal和and兼容。示例输入unit Unit1;interfac
  • 1) 2) 3)

    在这里插入图片描述
    在这里插入图片描述
    1)
    在这里插入图片描述
    2)

    在这里插入图片描述
    在这里插入图片描述
    3)

    在这里插入图片描述

    展开全文
  • 本篇文章给大家谈谈JavaScript抽象语法树。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。前言babel是现在几乎每个项目中必备的一个东西,但是其工作原理避不开对js的解析在生成的过程,babel有...

    本篇文章给大家谈谈JavaScript抽象语法树。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

    06b3db5410552143c799e2988b4ec5cd.png

    前言

    babel是现在几乎每个项目中必备的一个东西,但是其工作原理避不开对js的解析在生成的过程,babel有引擎babylon,早期fork了项目acron,了解这个之前我们先来看看这种引擎解析出来是什么东西。不光是babel还有webpack等都是通过javascript parser将代码转化成抽象语法树,这棵树定义了代码本身,通过操作这颗树,可以精准的定位到赋值语句、声明语句和运算语句

    什么是抽象语法树

    我们可以来看一个简单的例子:var a = 1;

    var b = a + 1;

    我们通过这个网站,他是一个esprima引擎的网站,十分好用.画成流程图如下:

    77262d98bf7d6c59e204b46ac4f73d25.png

    而他的json对象格式是这样的:{

    "type": "Program",

    "body": [

    {

    "type": "VariableDeclaration",

    "declarations": [

    {

    "type": "VariableDeclarator",

    "id": {

    "type": "Identifier",

    "name": "a"

    },

    "init": {

    "type": "Literal",

    "value": 1,

    "raw": "1"

    }

    }

    ],

    "kind": "var"

    },

    {

    "type": "VariableDeclaration",

    "declarations": [

    {

    "type": "VariableDeclarator",

    "id": {

    "type": "Identifier",

    "name": "b"

    },

    "init": {

    "type": "BinaryExpression",

    "operator": "+",

    "left": {

    "type": "Identifier",

    "name": "a"

    },

    "right": {

    "type": "Literal",

    "value": 1,

    "raw": "1"

    }

    }

    }

    ],

    "kind": "var"

    }

    ],

    "sourceType": "script"

    }

    众多的引擎

    chrome有v8,firefix有spidermonkey.还有一些常用的引擎有:esprima

    acron

    Traceur

    UglifyJS2

    shift

    下面是一些引擎的速度对比,以及用不同的框架,引擎们的加载速度:

    9a22ead81dfbbcca64c7ce75b9943412.png

    7e9b4d6ac0c44abcf3edbb1491a9e919.png

    605f2c525b9311851c1dd93b5757a6fb.png

    我个人认为,封装的越完美的,其实解析的时间更长,引擎之间也是acron这个速度比较优秀,babel引擎前身就是fork这个项目的。

    AST的三板斧通过esprima生成AST

    通过estraverse遍历和更新AST

    通过escodegen将AST重新生成源码

    我们可以来做一个简单的例子:

    1.先新建一个test的工程目录

    2.在test工程下安装esprima、estraverse、escodegen的npm模块npm i esprima estraverse escodegen --save

    3.在目录下面新建一个test.js文件,载入以下代码:const esprima = require('esprima');

    let code = 'const a = 1';

    const ast = esprima.parseScript(code);

    console.log(ast);

    你将会看到输出结果:Script {

    type: 'Program',

    body:

    [ VariableDeclaration {

    type: 'VariableDeclaration',

    declarations: [Array],

    kind: 'const' } ],

    sourceType: 'script' }

    4.再在test文件中,载入以下代码:const estraverse = require('estraverse');

    estraverse.traverse(ast, {

    enter: function (node) {

    node.kind = "var";

    }

    });

    console.log(ast);

    输出的结果:Script {

    type: 'Program',

    body:

    [ VariableDeclaration {

    type: 'VariableDeclaration',

    declarations: [Array],

    kind: 'var' } ],

    sourceType: 'script' }

    5.最后在test文件中,加入以下代码:const escodegen = require("escodegen");

    const transformCode = escodegen.generate(ast)

    console.log(transformCode);

    输出的结果:var a = 1;通过这三板斧:我们将const a = 1转化成了var a = 1

    有没有babel的感觉0.0

    推荐网站

    总结

    抽象树在前端用的很多很多,现在流行的工具,不管是webpack还是babel都会通过那个三板斧的流程,这里我只是大致介绍一下,过段时间,会出一篇抽象树的语法,有兴趣的也可以把esprima的源码看一下,为什么是esprima呢,因为esprima的资料比较多,而acron比较轻量级。

    本文转载自:https://segmentfault.com/a/1190000012943992

    更多web前端开发知识,请查阅 HTML中文网 !!

    展开全文
  • AST 是一种源代码的抽象语法结构的形表示。中的每个节点都表示源代码中出现的一个构造。 AST 的世界”里所有的一切都是 节点(Node),不同类型的节点之间相互嵌套形成一颗完整的形结构。 { "program": ...

    概念和基本结构

    AST 是一种源代码的抽象语法结构的树形表示。树中的每个节点都表示源代码中出现的一个构造。

    AST 的世界”里所有的一切都是 节点(Node),不同类型的节点之间相互嵌套形成一颗完整的树形结构。

    {
      "program": {
        "type": "Program",
        "sourceType": "module",
        "body": [
          {
            "type": "FunctionDeclaration",
            "id": {
              "type": "Identifier",
              "name": "foo"
            },
            "params": [
              {
                "type": "Identifier",
                "name": "x"
              }
            ],
            "body": {
              "type": "BlockStatement",
              "body": [
                {
                  "type": "IfStatement",
                  "test": {
                    "type": "BinaryExpression",
                    "left": {
                      "type": "Identifier",
                      "name": "x"
                    },
                    "operator": ">",
                    "right": {
                      "type": "NumericLiteral",
                      "value": 10
                    }
                  }
                }
              ]
            }
            ...
           }
           ...
        ]
    }

    AST 的结构不全一样,目前 JavaScript 编译器遵循的通用规范 —— ESTree 中对于 AST 结构的一些基本定义,不同的编译工具都是基于此结构进行了相应的拓展。

    生成方式

    计算机想要理解一串源代码需要经过一系列的分析过程:

    1. 词法分析 (Lexical Analysis) 

    词法分析阶段扫描输入的源代码字符串,生成一系列的词法单元 (tokens),这些词法单元包括数字,标点符号,运算符等。词法单元之间都是独立,该阶段并不关心代码的组合方式。

    2. 语法分析 (Syntax Analysis)

    语法分析阶段就会将上一阶段生成的 token 列表转换为如下图右侧所示的 AST,根据这个数据结构可看出转换之前源代码的基本构造;

    3. 代码生成 (Code Generation)

    代码生成阶段是一个非常自由的环节,可由多个步骤共同组成,此阶段可以遍历初始的 AST,对其结构进行改造,再将改造后的结构生成对应的代码字符串。

    用法与实战 

    AST 的应用场景:

    • 代码高亮、格式化、错误提示、自动补全等ESlintPrettierVetur等。
    • 代码压缩混淆uglifyJS等
    • 代码转译webpackbabelTypeScript等

    那如何使用 AST,其使用步骤:

    1. 解析 (Parsing):这个过程由编译器实现,会经过词法分析过程和语法分析过程,从而生成 AST
    2. 读取/遍历 (Traverse):深度优先遍历 AST ,访问树上各个节点的信息(Node)。
    3. 修改/转换 (Transform):在遍历的过程中可对节点信息进行修改,生成新的 AST
    4. 输出 (Printing):对初始 AST 进行转换后,根据不同的场景,既可以直接输出新的 AST,也可以转译成新的代码块。

    通常情况下使用 AST重点关注的是步骤2和3,诸如 Babel、ESLint 等工具暴露出来的通用能力都是对初始 AST 进行访问和修改。

    这两步的实现基于一种名为访问者模式的设计模式,即定义一个 visitor 对象,在该对象上定义了对各种类型节点的访问方法,这样就可以针对不同的节点做出不同的处理。例如,编写 Babel 插件其实就是在构造一个 visitor 实例来处理各个节点信息,从而生成想要的结果:

    const visitor = {
    
        CallExpression(path) {
    
            ...
    
        }
    
        FunctionDeclaration(path) {
    
            ...
    
        }   
    
        ImportDeclaration(path) {
    
            ...
    
        }
    
        ...
    
    }
    

    现在我们来看看如何使用 Bable 操作 AST。需要用到以下开发工具:

    • AST Explorer:在线 AST 转换工具,集成了多种语言和解析器
    • @babel/parser :将 JS 代码解析成对应的 AST
    • @babel/traverse:对 AST 节点进行递归遍历
    • @babel/types:集成了一些快速生成、修改、删除 AST Node的方法
    • @babel/generator :根据修改过后的 AST 生成新的 js 代码

    1. 将所有函数中的普通 log 打印转换成 error 打印,并在打印内容前方附加函数名的字符串

    /** 转换前 */
    function add(a, b) {
        console.log(a + b);
        return a + b;
    }
    
    /** 转换后 */
    function add(a, b) {
        console.error('add', a + b);
        return a + b;
    }
    

    实现思路:

    • 遍历所有的函数调用表达式(CallExpression)节点
    • 将函数调用方法的属性由 log 改为 error
    • 找到函数声明(FunctionDeclaration)父节点,提取函数名信息
    • 将函数名信息包装成字符串字面量(StringLiteral)节点,插入函数调用表达式的参数节点数组中
    const compile = (code) => {
      /** 解析成初始AST */
      const ast = parser.parse(code);
      const visitor = {
        /** 1. 遍历所有的函数调用表达式(CallExpression)节点 */
        CallExpression(path) {
          const { callee } = path.node;
          if (types.isCallExpression(path.node) && types.isMemberExpression(callee)) {
            const { object, property } = callee;
            if (object.name !== 'console' || property.name !== 'log') return;
            /** 2. 将成员表达式的属性由 log -> error */
            property.name = 'error';
            /** 3. 向上遍历,在该函数调用节点的父节点中找到函数声明节点 */
            const FunctionDeclarationNode = path.findParent(parent => {
              return parent.type === 'FunctionDeclaration';
            })
            /** 4. 提取函数名称信息,包装成一个字符串字面量节点,插入当前节点的参数数组中 */
            const funcNameNode = types.stringLiteral(FunctionDeclarationNode.node.id.name)
            path.node.arguments.unshift(funcNameNode);
          }
        }
      }
      traverse.default(ast, visitor);
      /** code generator */
      const newCode = generator.default(ast, {}, code).code;
    }

    2. 为所有的函数添加错误捕获,并在捕获阶段实现自定义的处理操作

    /** 处理前 */
    function add(a, b) {
      console.log('23333');
      throw new Error('233 Error');
      return a + b;
    }
    
    /** 处理后 */
    function add(a, b) {
      /** 这里只能捕获到同步代码的执行错误 */
      try {    
        console.log('23333');
        throw new Error('233 Error');
        return a + b;
      } catch (myError) {
          /** 自定义处理(eg:函数错误自动上报) */
          mySlardar(myError);
      }
    }

    思路:

    • 遍历函数声明(FunctionDeclaration)节点
    • 提取该节点下整个代码块节点,作为 try 语句tryStatement)处理块中的内容
    • 构造一个自定义的 catch 子句(catchClause)节点,作为 try 异常处理块的内容
    • 将整个 try 语句节点作为一个新的函数声明节点的子节点,用新生成的节点替换原有的函数声明节点
    const compile = (code) => {
    
      /** 解析成初始AST */
      const ast = parser.parse(code);
      /** 查看 ast 结果 */
      /** utils.writeAst2File(ast) **/ // 
      const visitor = {
        /** 1. 遍历所有的函数调用表达式(CallExpression)节点 */
        FunctionDeclaration(path) {
          const node = path.node
          const { params, id } = node;
          /** 2. 提取该节点下整个代码块节点,作为 try 语句(tryStatement)处理块中的内容 */
          const blockStatementNode = node.body;
          /** 已经有 try-catch 块的停止遍历,防止 circle loop */
          if (blockStatementNode.body && types.isTryStatement(blockStatementNode.body[0])) return;
          /** 3. 构造 catch 块节点和catch 子句节点 */
          const catchBlockStatement = types.blockStatement(
            [types.expressionStatement(
              types.callExpression(types.identifier('mySlardar'), [types.identifier('myError')])
            )]
          );
          const catchClause = types.catchClause(types.identifier('myError'), catchBlockStatement);
          /** try - catch 语句节点 */
          const tryStatementNode = types.tryStatement(blockStatementNode, catchClause);
          /** 4. try-catch 节点作为新的函数声明节点 */
          const tryCatchFunctionDeclare = types.functionDeclaration(id, params, types.blockStatement([tryStatementNode]));
          path.replaceWith(tryCatchFunctionDeclare);
        }
      }
      traverse.default(ast, visitor);
      /** code generator */
      const newCode = generator.default(ast, {}, code).code;
    }

    3. 在 webpack 中实现 import 的按需导入(简单版 babel-import-plugin)

    /** 处理前 */
    import { Button as Btn, Dialog } from '233_UI'
    import { HHH as hhh } from '233_UI'
    
    /**
     * 设置自定义参数: 
     * (moduleName) => `233_UI/lib/src/${moduleName}/${moduleName} `
     */
    
    /** 处理后 */
    import { Button as Btn } from "2333_UI/lib/src/Button/Button"
    import { Dialog } from "2333_UI/lib/src/Dialog/Dialog"
    import { HHH as hhh } from "2333_UI/lib/src/HHH/HHH"
    

    思路:

    • 在插件运行的上下文状态中指定自定义的查找文件路径规则
    • 遍历 import 声明节点(ImportDeclaration)
    • 提取 import 节点中所有被导入的变量节点(ImportSpecifier)
    • 将该节点的值通过查找文件路径规则生成新的导入源路径,有几个导入节点就有几个新的源路径
    • 组合被导入的节点和源头路径节点,生成新的 import 声明节点并替换
    const visitor = ({types}) => {
      return {
        visitor: {
          ImportDeclaration(path, {opts}) {
            /** 1. 通过插件的参数获取模块指定路径 */
            const _getModulePath = opts.moduleName;
            /** 2. 所有的 import 声明对象节点的来源 */
            const importSpecifierNodes = path.node.specifiers; 
            const importSourceNode = path.node.source; 
            /** 导入的来源路径 */
            const sourceNodePath = importSourceNode.value;
            /** import 节点来源路径不等于参数指定路径的,不做替换 */
            if (!opts.libaryName || sourceNodePath !== opts.libaryName) return;
            /** 3. 根据查找文件路径规则生成新的导入源路径 */
            const modulePaths = importSpecifierNodes.map(node => {
              return _getModulePath(node.imported.name);
            })
            /** 4. 组合被导入的节点和源头路径节点,生成新的 import 声明节点并替换 */
            const newImportDeclarationNodes = importSpecifierNodes.map((node, index) => {
              return types.importDeclaration([node], types.stringLiteral(modulePaths[index]));
            })
            path.replaceWithMultiple(newImportDeclarationNodes);
          }
        }
      }
    }
    
    const result = babel.transform(code, {
      plugins: [
        [
          visitor,
          {
            libaryName: '2333_UI',
            moduleName: moduleName => `2333_UI/lib/src/${moduleName}/${moduleName}`
          }
        ]
      ]
    })
    

    总结

    日常的开发或许较少的接触到AST,也不太有机会关注这些编译原理。但对AST的了解可以加快我们上手熟练这些开发工具和api。

    展开全文
  • AST(抽象语法树)超详细

    万次阅读 多人点赞 2019-07-15 17:15:10
    首先来一个比较形象的,转载自:AST-抽象语法树,讲述了为什么需要讲源代码转化为AST,总结就是:AST不依赖于具体的文法,不依赖于语言的细节,我们将源代码转化为AST后,可以对AST做很多的操作,...
  • Python的抽象语法树(二)在之前的Python 的抽象语法树(一),我们阐述了一个字符串表达式是如何按照Python的文法定义生成对应的抽象语法书的。今天我们就借助一个简单的小例子,来感受下Python生成抽象语法书的具体...
  • 1.语法制导定义 语法制导定义是一个上下文无关文法和属性及规则的结合。属性和文法符号相关联,而规则和产生式相关联。如果X是一个符号而a是X的一个属性,那么我们用X.a来表示在某个标号为X的分析结点的值。 2...
  • 原标题:前端码农之蜕变 — AST(抽象语法树)英文:Bohdan Liashenko 译文:岁月是把杀猪刀segmentfault.com/a/1190000017152442前言首先,先说明下该文章是译文,原文出自《AST for Java developers》...
  • sql 解析,编译,ast 抽象语法树

    万次阅读 2018-03-06 01:35:35
    `),中文是抽象语法树。在不说子查询之类的情况下,这个AST也不会太复杂,毕竟where后面的情况比起编译原理里的程序语言来说情况还是要少得多的。以上述的sql为例,这里解析出来的Where结构是这样的: & sqlparser ....
  • 不光是babel还有webpack等都是通过javascript parser将代码转化成抽象语法树,这棵树定义了代码本身,通过操作这颗树,可以精准的定位到赋值语句、声明语句和运算语句。 什么是抽象语法树 我们可以来看一个简单的...
  • 抽象语法树

    万次阅读 2016-09-01 11:18:10
    抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套...
  • AST-抽象语法树

    万次阅读 2015-05-14 09:49:49
    抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌
  • v8世界探险(3) - v8的抽象语法树结构

    千次阅读 2016-11-12 16:55:02
    v8的抽象语法树结构
  • 编译原理--抽象语法树AST

    千次阅读 2013-07-04 13:56:06
    http://blog.csdn.net/zhouhuozhi/article/details/4250258
  • 在正文之前,突然想发一点小感慨。因为懂得不多,又想自己探索,所以走了很多很多弯路。以前总听别人说,不要怕走弯路,全当鸡汤听了。现在发现,确实如此。参考:主要是青木峰郎的《自制... 总的来说,AST比par...
  • javascript编写一个简单的编译器(理解抽象语法树AST) 编译器是一种接收一段代码,然后把它转成一些其他一种机制。 我们现在来做一个在一张纸上出一条线,那么我们出一条线需要定义的条件如下: 使用 Paper定义...
  • 没有学过编译原理,讲到语法树,总是觉得很抽象,网上又找不到这方面的资料,于是把它的真实结构了出来。 树里面的节点都继承了JCTree这个类。而在一个文件的java程序中,节点起源于JCCompilationUnit,里面包含...
  • 【输出】语法树或者错误。 【题目】设计一个程序,输入字符串形式的源程序,输出该程序的语法分析树,有错误时报告错误。要求: 1.源语言及其语法规则:可以参照附录A,也可以自定义。 2.输入为字符串形式的源程序,...
  • 【SQL】SQL语法树

    万次阅读 2018-04-08 22:32:57
    1. 为什么会出现SQL语法树? 假设有一个SQL语句 select name,age,count(name) as count_name, count(id) as count_id from mytable where id = ? and name = ? 这是一条sql语句,如果你想运行这个语句(下面写...
  • Aspect 语法难懂?ASM 字节码操作繁琐?APT 难以精准找到切入点?你该试试 AST 了!编辑器级别,效率高,更轻量。 一、概念 在开始上手之前,我们先了解下几个简单的概念: 什么是 AST ?AST 的作用? 我们知道,...
  • 飘逸的python - 一个简单的AST(抽象语法树)

    千次阅读 多人点赞 2014-10-04 11:58:20
    假设对'a + 3 * b'进行解释,其中a=2,b=5 代码很简单,就不再进行详细的解释了。 Num = lambda env, n: n Var = lambda env, x: env[x] Add = lambda env, a, b:_eval(env, a) + _eval(env, b) ...
  • 四则运算--语法树、中缀表达式、波兰表达式、逆波兰表达式中缀表达式和语法树中缀表达式语法树前缀表达式(波兰式)中缀表达式生成前缀表达式根据前缀表达式计算结果后缀表达式(逆波兰式)中缀表达式生成后缀表达式...
  • 抽象语法树 上面这棵"树"是一种非常直观的方式,但也说明了代码是可以抽象成树的形式表示的。接下来我们以更细的粒度再绘制这棵树。 抽象语法树(AST)是源码语法结构的一种抽象表示,它以树状的形式表现代码的结构...
  • 原文:JavaScript的工作原理:解析、抽象语法树(AST)+ 提升编译速度5个技巧 作者:前端小智 Fundebug经授权转载,版权归原作者所有。 这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇。 如果你...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,122
精华内容 5,648
关键字:

抽象语法树怎么画