html转换成抽象语法树,AST介绍:解析html生成语法树

前言

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

什么是AST(抽象语法树)?

抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。

AST的作用?

解释器/编译器进行语法分析的基础

AST的使用场景?

JS:代码压缩、混淆、编译

CSS:代码兼容多版本

HTML:Vue中Virtual DOM的实现

如何获取AST?

uglify、acorn、bablyon、esprima、htmlparser2、postcss

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

目标图例: 2cc9604d5ef5766da86fcd6dcde35148.png

2. 语法分析

Synax Kind(https://github.com/estree/estree/blob/master/es5.md)

Parse Token

Build Node

Build Tree

目标图例:27fa1aea71a6ab22c0651b4b55833d50.png

脑图在线地址: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类型

f2eb337c49c399188511695636add7e3.png

词法分析状态

e099d0212a628f8635ee679ff4abe3d5.png

根据不同的token类型,执行不同的解析策略

27319cfa22148641eca6d01fe8e2310a.png

通过全局变量保存当前解析状态

876a6bad23fe8b0abbe39250a19fa448.png

通过tagchain构建树结构,闭合树

890d004e0561fb3e267ce0bc0a5057b4.png378bb6cdcbc0dcfa865989793a0e9060.png

2. postcss

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

3. esprima

https://github.com/jquery/esprima

应用

html用AST实现了什么?

爬虫:解析hrml页面

mpvue:将vue模板语法转换为小程序的wxml语法

CSS用AST实现了什么?

postcss:sass、less语法编译、autoprefixer

JS用AST实现了什么?

bable、uglify、browserify、代码分析

参考资料

AST相关

javascript编写一个简单的编译器

https://www.cnblogs.com/tugenhua0707/p/7759414.html

Browserify运行原理分析

http://www.alloyteam.com/2014/10/browserify-yun-xing-yuan-li-fen-xi/

抽象语法树在 JavaScript 中的应用

https://blog.csdn.net/meituantech/article/details/80062290

使用 Acorn 来解析 JavaScript

https://segmentfault.com/a/1190000007473065#articleHeader9

PostCSS

PostCSS GitHub地址

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

PostCSS API文档

http://api.postcss.org/Node.html

PostCSS是什么?

https://segmentfault.com/a/1190000003909268?utm_source=tag-newest

Html5Parser

Html5Parser GitHub地址

https://github.com/acrazing/html5parser

Html5Parser 作者详解

https://segmentfault.com/a/1190000010759220

Esprima

Esprima GitHub地址

https://github.com/jquery/esprima

Esprima 在线demo

http://esprima.org/demo/parse.html#

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值