AST介绍:解析html生成语法树

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aaaaaaliang/article/details/89113897

前言


这个名词一直不陌生,但是没有细致了解过,结合自己的项目,记录一下自己对AST的理解。

  1. 什么是AST(抽象语法树)?
    抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。
  2. AST的作用?
    解释器/编译器进行语法分析的基础
  3. AST的使用场景?
    JS:代码压缩、混淆、编译
    CSS:代码兼容多版本
    HTML:Vue中Virtual DOM的实现
  4. 如何获取AST?
    uglify、acorn、bablyon、esprima、htmlparser2、postcss
  5. AST标准
    https://github.com/estree/estree/blob/master/es5.md

认识一个解析器


电脑中,解析器(parser)是指一个程序,通常是编译器的部分,接收输入的顺序源程序指令、交互式联机命令、标记或者一些其它定义的接口,将它们打破分成几个部分(例如名词(对象),动词(方法)和他们的属性或者选项),然后能够被其它的编程控制(如在编译器中的其它组成)。

目标

/* ==== 解析器部分 begin ==== */
  
function parseCode(string) {
  /**
   * 这里做了一些事情讲JS代码转换为语法树
   * @string String JS代码
   *
   * return @Ast Object 抽象语法树
   * */
   
  do something
  return Ast
}
  
/* ==== 解析器部分 end ==== */
  
/* ==== 使用部分 begin ==== */
  
const CODE_STRING = "var USER = \'Yu Liang\';"
  
const RESULT = parseCode(CODE_STRING)
  
{
  type: 'Program',
  body: [
  {
    type: 'VariableDeclaration',
    declarations: [
      {
        type: 'VariableDeclarator',
        id: {
          type: 'Identifier',
          name: 'USER'
        },
        init: {
          type: 'Literal',
          value: 'Yu Liang',
          raw: '\'Yu Liang\''
        }
      }
    ],
    kind: 'var'
  }
],
  sourceType: 'script'
}
  
/* ==== 使用部分 end ==== */

解析思路

1. 词法分析

  • Token Kind

  • Tokenize

  • Tokens Array

    目标图例: 在这里插入图片描述

2. 语法分析

目标图例:在这里插入图片描述
脑图在线地址:http://naotu.baidu.com/file/b56218ac3b536fa883df18714bfb76a6?token=0e452e4756aaa207 密码:r41p

MVP(Minimum Viable Product 最小可行性产品)

MVP产品同学经常用到的一个名词,其实开发也很需要了解这个概念。

/**
 * Created by yuliang on 2018/3/19 17:18
 */
 
var esprima = require('esprima');
var CODE_STRING = "const answer = 'yuliang'";
 
// console.log(esprima.tokenize(CODE_STRING));
 
// 定义token 种类
const TOKEN_KIND = {
  Keyword: 'Keyword', //关键词
  Identifier: 'Identifier', //标识
  Punctuator: 'Punctuator', // 符号
  String: 'String', // 字符串
  WhiteSpace: 'WhiteSpace', // 字符串
  EOF: 'EOF' // 结束
};
// 定义获取token时的status
const TOKENIZE_STATUS = {
  keywordStart: 'keywordStart',
  keywordend: 'keywordend'
};
 
// todo 每一种token都会有的固定的属性,使用时继承下去,虽然我没做完
class _token {
  constructor(x, y) {
    this.start = x;
    this.end = y;
  }
}
 
function getCodeTokenKindFn(item) {
  if (['=', ';'].indexOf(item) >= 0) {
    // 符号
    return 'Punctuator';
  } else if (item === ' ') {
    // 空格
    return 'WhiteSpace';
  } else if (['const'].indexOf(item) >= 0) {
    // 字符-keyword
    return 'Keyword';
  } else {
    return 'String';
  }
}
 
// 获取代码的token列表
function tokenize(code) {
  let index = 0; // 用来记数 现在处理到第几个字符了
  const length = code.length; // 代码总字符长度
  // console.log('length', length);
 
  let TOKEN_ARRAY = []; //用来存储token的数组
  let STATUS = '';
  while (index < length) {
    const ITEM = code[index];
    // 应该用switch
    if (getCodeTokenKindFn(ITEM) === TOKEN_KIND.Punctuator) {
      // 处理多个符号连成串的情况 例如:==
      // if(){}
      // console.log(code[index], index);
      TOKEN_ARRAY.push({
        type: TOKEN_KIND.Punctuator,
        value: ITEM
      });
      index++;
    } else if (getCodeTokenKindFn(ITEM) === TOKEN_KIND.WhiteSpace) {
      // 空格情况
      // console.log(code[index], index);
      // TOKEN_ARRAY.push({
      //   type: TOKEN_KIND.WhiteSpace,
      //   value: ITEM
      // });
      index++;
    } else {
      // console.log(code[index], index);
      let _S = ITEM;
      while (
        index + 1 < length &&
        getCodeTokenKindFn(code[index + 1]) !== TOKEN_KIND.Punctuator &&
        getCodeTokenKindFn(code[index + 1]) !== TOKEN_KIND.WhiteSpace
      ) {
        _S = _S + code[index + 1];
        index++;
        // console.log('不是空格和符号', _S);
      }
      if (getCodeTokenKindFn(_S) === TOKEN_KIND.Keyword) {
        STATUS = TOKENIZE_STATUS.keywordend;
        TOKEN_ARRAY.push({
          type: TOKEN_KIND.Keyword,
          value: _S
        });
      } else if (STATUS === TOKENIZE_STATUS.keywordend) {
        STATUS = '';
        TOKEN_ARRAY.push({
          type: TOKEN_KIND.Identifier,
          value: _S
        });
      } else {
        TOKEN_ARRAY.push({
          type: TOKEN_KIND.String,
          value: _S
        });
      }
      index++;
    }
  }
  return TOKEN_ARRAY;
}
 
function getSyntaxKindFn(keyword) {
  return { const: 'VariableDeclaration', var: 'VariableDeclaration' }[keyword];
}
// 定义获取token时的status
const ANALYSIS_STATUS = {
  VariableDeclarationStart: 'VariableDeclarationStart',
  VariableDeclarationEnd: 'VariableDeclarationEnd',
  VariableDeclarationStart: ''
};
// 分析tokens 返回语法树
function analysis(tokens) {
  let index = 0; // 当前处理到token的位置
  const length = tokens.length; // tokens数组长度
  let TREE = { type: 'Program', body: [], sourceType: 'script' }; //语法树
  let CURRENT_STATUS = '';
  let CURRENT_NODE = {};
  while (index < length) {
    const TOKEN = tokens[index];
    // console.log(length, index, TOKEN);
    CURRENT_STATUS = '';
    CURRENT_NODE = {};
    debugger;
    switch (getSyntaxKindFn(TOKEN.value)) {
      case 'VariableDeclaration':
        if (['const'].indexOf(TOKEN.value) >= 0) {
          let NODE = {
            type: 'VariableDeclaration',
            declarations: [],
            kind: 'const'
          };
          let ID = {};
          let INIT = {};
 
          if (tokens[index + 1].type === 'Identifier') {
            ID = { type: 'Identifier', name: tokens[index + 1].value };
            index++;
          }
 
          if (tokens[index + 1].type !== 'Punctuator') {
            throw new Error('var 语法错误');
            return;
          } else {
            index++;
          }
          if (tokens[index + 1].type === 'String') {
            INIT = {
              type: 'Literal',
              value: tokens[index + 1].value,
              raw: tokens[index + 1].value
            };
            index++;
          } else {
            throw new Error('var 语法错误');
            return;
          }
          NODE.declarations.push({
            type: 'VariableDeclarator',
            id: ID,
            init: INIT
          });
          CURRENT_NODE = NODE;
          index++;
        }
        TREE.body.push(CURRENT_NODE);
        break;
      default:
        console.log('不识别的语法');
        index++;
        break;
    }
  }
  return TREE;
}
 
function parseCode(code) {
  const TOKEN_ARRAY = tokenize(code);
  return analysis(TOKEN_ARRAY);
}
 
module.exports = { parseCode, analysis, tokenize };

源码解析

1. html5parser
https://github.com/acrazing/html5parser

  • token类型
    在这里插入图片描述
  • 词法分析状态
    在这里插入图片描述
  • 根据不同的token类型,执行不同的解析策略
    在这里插入图片描述
  • 通过全局变量保存当前解析状态
    在这里插入图片描述
  • 通过tagchain构建树结构,闭合树
    在这里插入图片描述在这里插入图片描述

2. postcss
https://github.com/postcss/postcss/blob/HEAD/README-cn.md

3. esprima
https://github.com/jquery/esprima

应用

  1. html用AST实现了什么?
    爬虫:解析hrml页面
    mpvue:将vue模板语法转换为小程序的wxml语法
  2. CSS用AST实现了什么?
    postcss:sass、less语法编译、autoprefixer
  3. JS用AST实现了什么?
    bable、uglify、browserify、代码分析

参考资料

AST相关

  1. javascript编写一个简单的编译器
    https://www.cnblogs.com/tugenhua0707/p/7759414.html
  2. Browserify运行原理分析
    http://www.alloyteam.com/2014/10/browserify-yun-xing-yuan-li-fen-xi/
  3. 抽象语法树在 JavaScript 中的应用
    https://blog.csdn.net/meituantech/article/details/80062290
  4. 使用 Acorn 来解析 JavaScript
    https://segmentfault.com/a/1190000007473065#articleHeader9

PostCSS

  1. PostCSS GitHub地址
    https://github.com/postcss/postcss/blob/HEAD/README-cn.md
  2. PostCSS API文档
    http://api.postcss.org/Node.html
  3. PostCSS是什么?
    https://segmentfault.com/a/1190000003909268?utm_source=tag-newest

Html5Parser

  1. Html5Parser GitHub地址
    https://github.com/acrazing/html5parser
  2. Html5Parser 作者详解
    https://segmentfault.com/a/1190000010759220

Esprima

  1. Esprima GitHub地址
    https://github.com/jquery/esprima
  2. Esprima 在线demo
    http://esprima.org/demo/parse.html#

猜你喜欢

转载自blog.csdn.net/aaaaaaliang/article/details/89113897