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
目标图例:
2. 语法分析
- Synax Kind(https://github.com/estree/estree/blob/master/es5.md)
- Parse Token
- Build Node
- Build Tree
目标图例:
脑图在线地址: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
应用
- 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.****.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#