1. Абстрактное синтаксическое дерево
Абстрактное синтаксическое дерево
Ядро многих инструментов и библиотек, таких как webpack и Link, основано на концепции абстрактного синтаксического дерева для реализации проверки кода, анализа и других операций.
Вы также можете написать подобные инструменты, понимая концепцию абстрактного грамматического дерева.
2. Использование абстрактного синтаксического дерева
- Проверка синтаксиса кода, проверка стиля кода, форматирование кода, подсветка кода, подсказки об ошибках кода, автодополнение кода и т. д.
- Например, JSLint, JSHint для проверки ошибок кода или стиля, поиска возможных ошибок.
- Подсказки об ошибках IDE, форматирование, выделение, автозаполнение и т. д.
- обфускация кода
- UglifyJS2 и т. д.
- Оптимизируйте код изменений, измените структуру кода для достижения желаемой структуры
- Инструменты упаковки кода webpack, rollup и т. д.
- Преобразование между спецификациями кода, такими как CommonJS, AMD, CMD, UMD и т. д.
- Конвертируйте CoffeeScript, TypeScript, JSX и т. д. в собственный Javascript
3. Определение абстрактного синтаксического дерева
Принцип этих инструментов заключается в преобразовании кода в абстрактное синтаксическое дерево (AST) с помощью синтаксического анализатора Javascript. Это дерево определяет структуру кода. Манипулируя этим деревом, мы можем точно определить местонахождение оператора объявления, оператора присваивания и оператора операции. , И так далее, чтобы добиться анализа кода, оптимизации, изменений и других операций.
В информатике абстрактное синтаксическое дерево (сокращенно абстрактное синтаксическое дерево или AST) или синтаксическое дерево (синтаксическое дерево) представляет собой древовидное представление абстрактной синтаксической структуры исходного кода, в частности исходного кода языка программирования. .
Синтаксис Javascript предназначен для лучшего программирования для разработчиков, но он не подходит для понимания программы. Поэтому его необходимо преобразовать в AST, чтобы сделать его более подходящим для анализа программы.Браузерные компиляторы обычно конвертируют исходный код в AST для дальнейшего анализа и других операций.
var AST = "is Tree";
{
"type": "Program",
"body": [{
"type": "VariableDeclaration",
"kind": "var",
"declarations": [{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "AST"
},
"init": {
"type": "Literal", //文本格式
"value": "is Tree",
"raw": "\"is Tree\""
}
}]
}]
}
https://astexplorer.net/
4. Парсер JavaScript
- JavaScript Parser, парсер, который преобразует исходный код js в абстрактное синтаксическое дерево.
- Браузер преобразует исходный код js в абстрактное синтаксическое дерево через парсер, а затем дополнительно преобразует его в байт-код или напрямую генерирует машинный код.
- Вообще говоря, каждый движок js будет иметь свой собственный формат абстрактного синтаксического дерева, движок Chrome v8, движок FireFox SpiderMonkey и т. д. MDN предоставляет подробное описание формата SpiderMonkey AST, который является отраслевым стандартом.
4.1 Обычно используемые инструменты парсера JavaScript:
- esprima
- traceur
- acorn
- shift
4.2 esprima
- Преобразование исходного кода в AST через esprima
- Пройдите и обновите AST через estraverse
- Регенерация исходного кода из AST через escodegen
- инструмент визуализации astexplorer AST
npm install esprima estraverse escodegen
let esprima = require('esprima'); //源代码转成AST语法树
let estraverse = require('estraverse'); //遍历语法树
let escodegen = require('escodegen'); //把AST语法树重新生成代码的工具
let sourceCode = 'function ast(){}'
let ast = esprima.parse(sourceCode);
let indent = 0;
function pad(){
return " ".repeat(indent)
}
estraverse.traverse(ast,{
enter(node){
console.log(pad() + node.type);
indent += 2;
},
leave(node){
indent -= 2;
console.log(pad() + node.type)
}
})
5. Функция стрелки преобразования
- Режим посетителя Посетитель Для объекта или группы объектов разные посетители дают разные результаты и выполняют разные операции.
- @babel/core Компилятор Babel, в нем есть все основные API, такие как общее преобразование, синтаксический анализ
- Парсер для вавилона Babel
- babel-typesБиблиотека инструментов, подобная Lodash, для узлов AST, которая содержит методы построения, проверки и преобразования узлов AST, полезные для написания логики для обработки AST.
- babel-traverseИспользуется для обхода AST, поддержания состояния всего дерева и отвечает за замену, удаление и добавление узлов.
- babel-types-api
- Руководство по плагину Babel
- babeljs.ioвизуальный редактор babel
- babel-plugin-transform-es2015-arrow-functionsФункция стрелки преобразования Бабеля
npm install @babel/core babel-types -D
let babel = require('@babel/core'); //用来生成语法树,并且遍历转化语法树
let types = require('babel-types'); //用来生成新节点,或者判断某个节点是否是某个类型
const { generate } = require('escodegen');
const sourceCode = `const sum = (a,b) => a+b`;
//插件的结构
let transformArrayFunction = {
visitor: { //访问者模式;可以访问源代码生成的语法树所有节点,捕获特定节点
//捕获箭头函数表达式,转成普通函数
ArrowFunctionExpression: (path,state) => {
let id = path.parent.id; //path.node代表当前节点,path.parent代表父节点
let arrowNode = path.node;
let params = arrowNode.params;
let body = arrowNode.body; //BinaryExpression
let generator = arrowNode.generator;
let async = arrowNode.async;
//types.blockStatement 生成一个函数体
let functionExpression = types.functionExpression(id,params,types.blockStatement([types.returnStatement(body)]),generator,async)
path.replaceWith(functionExpression); //将转化的新节点替换老节点
}
}
}
let result = babel.transform(sourceCode,{
plugins: [transformArrayFunction],
});
console.log(result);
{
metadata: {},
options: {
babelrc: false,
configFile: false,
passPerPreset: false,
envName: 'development',
cwd: 'd:\\111前端优选\\TODO\\ast',
root: 'd:\\111前端优选\\TODO\\ast',
plugins: [ [Plugin] ],
presets: [],
parserOpts: { sourceType: 'module', sourceFileName: undefined, plugins: [] },
generatorOpts: {
filename: undefined,
auxiliaryCommentBefore: undefined,
auxiliaryCommentAfter: undefined,
retainLines: undefined,
comments: true,
shouldPrintComment: undefined,
compact: 'auto',
minified: undefined,
sourceMaps: false,
sourceRoot: undefined,
sourceFileName: 'unknown'
}
},
ast: null,
code: 'const sum = function sum(a, b) {\n return a + b;\n};', //结果
map: null,
sourceType: 'module'
}
Работа с синтаксическим деревом состоит из трех шагов:
- Генерация синтаксического дерева из исходного кода
- преобразовать синтаксическое дерево
- Генерация преобразованного кода на основе синтаксического дерева
6. АСТ
1. Процесс анализа
Весь процесс парсинга AST делится на два этапа:
- Сегментация слов, которая разбивает всю строку кода на массив синтаксических единиц.
- Синтаксический анализ, установление связи между анализируемыми грамматическими единицами
2. Синтаксический блок
Синтаксические единицы в коде Javascript в основном включают следующее:
- Ключевые слова:
const
, пусть, вар и т. д. - Идентификатор: может быть переменной, ключевым словом if/else или константой true/false.
- оператор
- количество
- пространство
- Примечания
3. Лексический анализ
let sourceCode = `let element = <h1>hello</h1>`;
/**
* 1.分词,把token拆开 词法分析,就是把代码转成一个token数组
*/
function lexical(code){
const tokens = [];
for(let i=0;i<code.length;i++){
let ch = code.charAt(i); //l i=3 ch=空格
if(/[a-zA-Z_]/.test(ch)){ //判断是否为合理变量名、标识符
const token = {type: 'Indentifier',value: ch};
tokens.push(token);
for(i++;i<code.length;i++){ //再向后移,判断是不是英文字母
ch = code.charAt(i); //i=1 ch=e
if(/[a-zA-Z_]/.test(ch)){
token.value += ch; //value=l value=le value=let
}else{ //i=3 ch=空格
if(token.value == 'let'){
token.type = 'KeyWord';
}
i--; //将空格回减
break;
}
}
continue;
}else if(/\s/.test(ch)){ //如果ch是空格的话
const token = {
type: "WhiteSpace",
value: " "
}
tokens.push(token);
for(i++;i<code.length;i++){
ch = code.charAt(i);
if(/\s/.test(ch)){
token.value += ch;
}else{ //关键字和变量名之间的空格(多个)结束
i--;
break;
}
}
continue;
}else if(ch == '='){
const token = {
type: "Equal",
value: "="
};
tokens.push(token);
}else if(ch == '<'){
const token = {
type: 'JSXElement', //遇到小于号,则为JSX元素
value: ch
}
tokens.push(token); //<h1>hello</h1>
let isClose = true; //判断是否遇到闭合标签 <hr/> <h1></h1>
for(i++;i<code.length;i++){
ch = code.charAt(i); //ch = h
token.value += ch;
if(ch == "/"){
isClose = true; //遇到斜杠时则下一个大于号则为闭合标签
}
if(ch == ">"){ //说明标签结束
if(isClose){
break;
}
}
}
continue;
}
}
return tokens;
}
let tokens = lexical(sourceCode);
console.log(tokens);
/**
[
{ type: 'KeyWord', value: 'let' },
{ type: 'WhiteSpace', value: ' ' },
{ type: 'Indentifier', value: 'element' },
{ type: 'WhiteSpace', value: ' ' },
{ type: 'Equal', value: '=' },
{ type: 'WhiteSpace', value: ' ' },
{ type: 'JSXElement', value: '<h1>' },
{ type: 'Indentifier', value: 'hello' },
{ type: 'JSXElement', value: '</h1>' }
]
*/
4. Синтаксический анализ
- Семантический анализ заключается в составлении объемных комбинаций полученных слов для определения связи между словами.
- Проще говоря, синтаксический анализ — это идентификация операторов и выражений, что является рекурсивным процессом.
function parse(tokens){
let ast = {
type: 'Program',
body: [],
sourceType: 'module'
};
let i = 0; //当前的索引
let currentToken; //当前的token
while(currentToken = tokens[i]){
//第一次的时候 currentToken = { type: 'KeyWord', value: 'let' }
if(currentToken.type == 'KeyWord' && currentToken.value == 'let'){ //或者var/const
let VariableDeclaration = {
type: 'VariableDeclaration',declarations: []
};
ast.body.push(VariableDeclaration);
i+=2; //{ type: 'Indentifier', value: 'element' },
currentToken = tokens[i];
let variableDeclarator = {
type: 'VariableDeclarator',
id: {
type: 'Indentifier',name: currentToken.value
},
}
VariableDeclaration.declarations.push(variableDeclarator);
i+=2; //i=4 //
currentToken = tokens[i]; //{ type: 'JSXElement', value: '<h1>hello</h1>' },
if(currentToken.type == "String"){
variableDeclarator.init = {type: 'StringLiteral',value: currentToken.value}
}else if(currentToken.type == "JSXElement"){
let value = currentToken.value;
let [,type,children] = value.match(/<([^>]+?)>([^>]+)<\/\1>/); //<h1></h1> type=h1 children=hello
variableDeclarator.init = {
type: 'JSXElement', //JSX元素
openingElement: {
type: 'openingElement',
name: {
type: 'JSXIndetifier',
name: type
}
},
closingElement: {
type: 'closingElement',
name: {
type: 'JSXIndentifier',
name: type
}
},
children: [
{type: 'JSXElement',value: children}
]
}
}
}
i++;
}
return ast;
}
let tokens = [
{ type: 'KeyWord', value: 'let' },
{ type: 'WhiteSpace', value: ' ' },
{ type: 'Indentifier', value: 'element' },
{ type: 'WhiteSpace', value: ' ' },
{ type: 'Equal', value: '=' },
{ type: 'WhiteSpace', value: ' ' },
{ type: 'JSXElement', value: '<h1>hello</h1>' }
]
let ast = parse(tokens);
ast.body[0].declarations[0].init = {
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "Indentifier",
"name": "React"
},
"property": {
"type": "Indentifier",
"name": "createElement"
}
},
"arguments": [
{
"type": "Literal",
"value": "h1",
"raw": "\"h1\""
},
{
"type": "Literal",
"value": null,
"raw": "null"
},
{
"type": "Literal",
"value": "hello",
"raw": "\"hello\""
}
]
}
}
console.log(JSON.stringify(ast));
/**
* {"type":"Program","body":[{"type":"VariableDeclaration","declarations":[{"type":"VariableDeclarator","id":{"type":"Indentifier","name":"element"},"init":{"type":"ExpressionStatement","expression":{"type":"CallExpression","callee":{"type":"MemberExpression","computed":false,"object":{"type":"Indentifier","name":"React"},"property":{"type":"Indentifier","name":"createElement"}},"arguments":[{"type":"Literal","value":"h1","raw":"\"h1\""},{"type":"Literal","value":null,"raw":"null"},{"type":"Literal","value":"hello","raw":"\"hello\""}]}}}]}],"sourceType":"module"}
*/
До сих пор преобразование синтаксического дерева было реализовано просто, в основном в трех шагах разбора исходного кода в синтаксическое дерево с точки зрения мышления. Благодаря анализу принципа синтаксического дерева нам будет полезно проанализировать принцип Babel, Webpack и других подключаемых модулей компиляции и применять его в повседневной разработке.
Для получения дополнительной информации о внешнем интерфейсе добро пожаловать в поиск и обратите внимание на общедоступную учетную запись [Оптимизация внешнего интерфейса]
В этой статье используетсяmdniceнабор текста