Babel(抽象语法树,又称AST)

文章:https://juejin.im/post/5a9315e46fb9a0633a711f25

https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md

  1. 你了解过Babel吗?

了解过抽象语法树,又称AST,有学习过,也写过一个基于AST的乞丐版模板引擎,先是词法解析token,然后生产抽象语法树,然后更改抽象语法树,当然这是插件做的事情,最后根据新的AST生成代码。

  1. 写过Babel插件吗

没有,只是看过相关文档

  1. 如果让你写一个插件,你能写的出来吗?

应该可以吧...

遂卒....

开玩笑的,既然提到了,又没回答上来什么,哎哟我这暴脾气,一想到今晚就睡不着,连夜把它撸了。

那么我们来从零写个插件吧。

写一个预计算简单表达式的插件

预览

Before:

const result = 1 + 2 + 3 + 4 + 5;

After:

const result = 15;

以上的例子可能大家不会经常遇到,因为傻x才会这么写,但是有可能你会这么写

setTimeout(function(){
  // do something
}, 1000 * 2) // 插件要做的事,就是把 1000 * 2 替换成 2000

前提条件

开工

再写代码之前,你需要明白Babel它的原理,简单点说: Babel解析成AST,然后插件更改AST,最后由Babel输出代码

那么Babel的插件模块需要你暴露一个function,function内返回visitor

module.export = function(babel){
  return {
    visitor:{
    }
  }
}

visitor是对各类型的AST节点做处理的地方,那么我们怎么知道Babel生成了的AST有哪些节点呢?

很简单,你可以把Babel转换的结果打印出来,或者这里有传送门: AST explorer

这里我们看到 const result = 1 + 2中的1 + 1是一个BinaryExpression节点,那么在visitor中,我们就处理这个节点

var babel = require(‘babel-core‘);
var t = require(‘babel-types‘);

const visitor = {
  BinaryExpression(path) {
    const node = path.node;
    let result;
    // 判断表达式两边,是否都是数字
    if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
      // 根据不同的操作符作运算
      switch (node.operator) {
        case "+":
          result = node.left.value + node.right.value;
          break
        case "-":
          result = node.left.value - node.right.value;
          break;
        case "*":
          result =  node.left.value * node.right.value;
          break;
        case "/":
          result =  node.left.value / node.right.value;
          break;
        case "**":
          let i = right;
          while (--i) {
            result = result || node.left.value;
            result =  result - node.left.value;
          }
          break;
        default:
      }
    }

    // 如果上面的运算有结果的话
    if (result !== undefined) {
      // 把表达式节点替换成number字面量
      path.replaceWith(t.numericLiteral(result));
    }
  }
};

module.exports = function (babel) {
  return {
    visitor
  };
}

插件写好了,我们运行下插件试试

const babel = require("babel-core");

const result = babel.transform("const result = 1 + 2;",{
  plugins:[
    require("./index")
  ]
});

console.log(result.code); // const result = 3;

与预期一致,那么转换 const result = 1 + 2 + 3 + 4 + 5;呢?

结果是: const result = 3 + 3 + 4 + 5;

这就奇怪了,为什么只计算了1 + 2之后,就没有继续往下运算了吗?

我们看一下这个表达式的AST树

你会发现Babel解析成表达式里面再嵌套表达式。

表达式( 表达式( 表达式( 表达式(1 + 2) + 3) + 4) + 5)

而我们的判断条件并不符合所有的,只符合1 + 2

    // 判断表达式两边,是否都是数字
    if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {}

那么我们得改一改

第一次计算1 + 2之后,我们会得到这样的表达式

表达式( 表达式( 表达式(3+ 3) + 4) + 5)

其中 3 + 3又符合了我们的条件, 我们通过向上递归的方式遍历父级节点

    // 如果上面的运算有结果的话
    if (result !== undefined) {
      // 把表达式节点替换成number字面量
      path.replaceWith(t.numericLiteral(result));

      let parentPath = path.parentPath;

      // 向上遍历父级节点
      parentPath && visitor.BinaryExpression.call(this, parentPath);
    }

到这里,我们就得出了结果 const result = 15;

那么其他运算呢:

const result = 100 + 10 - 50 >>> const result = 60;

const result = (100 / 2) + 50 >>> const result = 100;

const result = (((100 / 2) + 50 * 2) / 50) ** 2 >>> const result = 9;

完结

到这里,已经向你大概的讲解了,如果编写一个Babel插件,再也不怕面试官问我答不出什么了哈...

你以为这就完了吗?

并没有

如果转换这样呢: const result = 0.1 + 0.2;

预期肯定是0.3, 但是实际上,Javascript有浮点计算误差,得出的结果是0.30000000000000004

那是不是这个插件就没卵用?

这就需要你去矫正浮点运算误差了,可以使用Big.js;

比如: result = node.left.value + node.right.value; 改成 result = +new Big(node.left.value).plus(node.right.value);

你以为完了吗? 这个插件还可以做很多

比如: Math.PI * 2 >>> 6.283185307179586

比如: Math.pow(2,2) >>> 4

...

...

最后上项目地址: github.com/axetroy/bab…

作者:Axetroy
链接:https://juejin.im/post/5a9315e46fb9a0633a711f25
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文地址:https://www.cnblogs.com/zhaobao1830/p/8479459.html

时间: 2024-08-28 18:28:16

Babel(抽象语法树,又称AST)的相关文章

五分钟了解抽象语法树(AST)babel是如何转换的?

抽象语法树 什么是抽象语法树? It is a hierarchical program representation that presents source code structure according to the grammar of a programming language, each AST node corresponds to an item of a source code. 抽象语法树是源代码语法结构的一种抽象表示.它以树状的形式表现编程语言的语法结构,树上的每个节点

JavaScript的工作原理:解析、抽象语法树(AST)+ 提升编译速度5个技巧

这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! JavaScript 是如何工作的:深入V8引擎&编写优化代码的5个技巧! JavaScript 是如何工作的:内存管理+如何处理4个常见的内存泄漏 ! JavaScript 是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式! JavaScript 是如何工作的:

抽象语法树(Abstract Syntax Tree)

抽象语法树(AST)表示组成程序的结构,可以让程序员更容易使用,F# 适宜这种开发的一个原因就是它的联合类型.这种类型非常适合表示语言,因为它可以用来表示相关而结构不相同的项目.下面就是抽象语法树的例子: type Ast = | Ident of string | Val of System.Double | Multi of Ast * Ast | Div of Ast * Ast | Plus of Ast * Ast | Minus of Ast * Ast 树非常简单,只包含一种类型:

AST 抽象语法树

提起 AST 抽象语法树,大家可能并不感冒.但是提到它的使用场景,也许会让你大吃一惊.原来它一直在你左右与你相伴,而你却不知. 一.什么是抽象语法树 在计算机科学中,抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码.树上的每个节点都表示源代码中的一种结构. 之所以说语法是「抽象」的,是因为这里的语法并不会表示出真实语法中出现的每个细节. 二.使用场景 JS 反编译,语法

使用PHP-Parser生成AST抽象语法树

0.前言 最近项目的流程逐渐清晰,但是很多关键性的技术没有掌握,也只能一步一步摸索. 由于要做基于数据流分析的静态代码分析,所以前端的工作如:词法分析.语法分析必不可少.Yacc和Lex什么的就不再考虑了,查了一天的资料,发现两款比较适合,一款是Java下的ANTLR,另一款是专门做PHP AST生成的PHP-Parser. ANTLR是编译原理领域比较著名的工具了,相对于Yacc和Lex,更加实用.但是对PHP的语法文件只有一个,折腾了半天才生成调通,发现不太适合,对于"$a=1"生

AST抽象语法树 Javascript版

在javascript世界中,你可以认为抽象语法树(AST)是最底层. 再往下,就是关于转换和编译的"黑魔法"领域了. 现在,我们拆解一个简单的add函数 function add(a, b) { return a + b } 首先,我们拿到的这个语法块,是一个FunctionDeclaration(函数定义)对象. 用力拆开,它成了三块: 一个id,就是它的名字,即add 两个params,就是它的参数,即[a, b] 一块body,也就是大括号内的一堆东西 add没办法继续拆下去了

复杂网络,抽象语法树

近期看了一些软件抽象为复杂网络,以及软件抽象成静态语法树的文章.做一个小总结. 1.复杂网络是由大量的边和点组成的,边点都可以有类型,加权值,边还可以有方向.如何计算边和点的权值是一个关键点,如何在不执行代码的情况下确定边的方向,目前不确定是否已经解决. 有许多工具,可以直接扫描软件源代码,抽象为复杂网络.然而我还没亲身实践,且做个记录. Dependency Finder分析编译后的java代码,能够提取依赖图. Doxygen是使用c++开发的基于源代码注释的文档生成工具.但是这个注释,是人

解释抽象语法树

创建了抽象语法树之后,有两个选择:解释或编译.解释,简单地说,就是遍历树,同时执行操作:编译,就是改变成其他形式,对于机器执行来说可能更简单,通常可能更快.这一小节先讨论如何解释结果,下面一小节再讨论编译的内容,最后,再讨论何时应该用解释,何时应该用编译的问题. 下面的例子是一个很小解释器,解释抽象语法树的主要工作由函数interpret 完成,它遍历树,并同时执行需要的动作.逻辑相当简单,如果发现一个文字值或标识符,就返回相应值: | Ident (s) ->variableDict.[s]

编译器开发系列--Ocelot语言1.抽象语法树

从今天开始研究开发自己的编程语言Ocelot,从<自制编译器>出发,然后再自己不断完善功能并优化. 编译器前端简单,就不深入研究了,直接用现成的一款工具叫JavaCC,它可以生成抽象语法树,抽象语法树是生成中间代码的关键,而中间代码又是生成后端代码的关键. 整个编译器代码采用java语言编写,主要功能是对JavaCC生成的抽象语法树进行语义分析.优化,最后生成优化后的汇编代码,然后再用汇编器对汇编代码汇编生成机器码,最后再用命令链接生成Linux可执行文件,就可以直接在Linux上运行了. 整