We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
最近给团队分享了一篇babel原理,然后我把他整理成一篇blog,本篇总字数6059(含代码),速读3分钟,普通阅读5分钟,有兴趣的可以关注一下我的github博客
我们来看一段代码:
[1,2,3].map(n => n + 1);
经过babel之后,这段代码变成了这样:
[1, 2, 3].map(function (n) { return n + 1; });
babel的过程:解析——转换——生成。
这边又一个中间的东西,是抽象语法树(AST)
一个js语句是怎么被解析成AST的呢?这个中间有两个步骤,一个是分词,第二个是语义分析,怎么理解这两个东西呢?
什么叫分词?
比如我们在读一句话的时候,我们也会做分词操作,比如:“今天天气真好”,我们会把他切割成“今天”,“天气”,“真好”。
那换成js的解析器呢,我们看一下下面一个语句console.log(1);,js会看成console,.,log,(,1,),;。
console.log(1);
console
.
log
(
1
)
;
所以我们可以把js解析器能识别的最小词法单元。
当然这样的分词器我们可以简易实现一下。
//思路分析:传入的是字符串的参数,然后每次取一个字符去校验,用if语句去判断,然后最后结果存入一个数组中,对于标识符和数字进行特殊处理 function tokenCode(code) { const tokens = []; //字符串的循环 for(let i = 0; i < code.length; i++) { let currentChar = code.charAt(i); //是分号括号的情况 if (currentChar === ';' || currentChar === '(' || currentChar === ')' || currentChar === '}' || currentChar === '{' || currentChar === '.' || currentChar === '=') { // 对于这种只有一个字符的语法单元,直接加到结果当中 tokens.push({ type: 'Punctuator', value: currentChar, }); continue; } //是运算符的情况 if (currentChar === '>' || currentChar === '<' || currentChar === '+' || currentChar === '-') { // 与上一步类似只是语法单元类型不同 tokens.push({ type: 'operator', value: currentChar, }); continue; } //是双引号或者单引号的情况 if (currentChar === '"' || currentChar === '\'') { // 引号表示一个字符传的开始 const token = { type: 'string', value: currentChar, // 记录这个语法单元目前的内容 }; tokens.push(token); const closer = currentChar; // 进行嵌套循环遍历,寻找字符串结尾 for (i++; i < code.length; i++) { currentChar = code.charAt(i); // 先将当前遍历到的字符无条件加到字符串的内容当中 token.value += currentChar; if (currentChar === closer) { break; } } continue; } if (/[0-9]/.test(currentChar)) { // 数字是以0到9的字符开始的 const token = { type: 'number', value: currentChar, }; tokens.push(token); for (i++; i < code.length; i++) { currentChar = code.charAt(i); if (/[0-9\.]/.test(currentChar)) { // 如果遍历到的字符还是数字的一部分(0到9或小数点) // 这里暂不考虑会出现多个小数点以及其他进制的情况 token.value += currentChar; } else { // 遇到不是数字的字符就退出,需要把 i 往回调, // 因为当前的字符并不属于数字的一部分,需要做后续解析 i--; break; } } continue; } if (/[a-zA-Z\$\_]/.test(currentChar)) { // 标识符是以字母、$、_开始的 const token = { type: 'identifier', value: currentChar, }; tokens.push(token); // 与数字同理 for (i++; i < code.length; i++) { currentChar = code.charAt(i); if (/[a-zA-Z0-9\$\_]/.test(currentChar)) { token.value += currentChar; } else { i--; break; } } continue; } if (/\s/.test(currentChar)) { // 连续的空白字符组合到一起 const token = { type: 'whitespace', value: currentChar, }; // 与数字同理 for (i++; i < code.length; i++) { currentChar = code.charAt(i); if (/\s]/.test(currentChar)) { token.value += currentChar; } else { i--; break; } } continue; } throw new Error('Unexpected ' + currentChar); } return tokens; }
语义分析的话就比较难了,为什么这么说呢?
因为这个不像分词这样有个标准,有些东西都要靠自己去摸索。
其实语义分析分为两块,一块是语句,还有一块是表达式。
什么叫语句?什么叫表达式呢?
表达式,比如:a > b; a + b;这一类的,可以嵌套,也可以运用在语句中。
a > b; a + b;
语句,比如:var a = 1, b = 2, c =3;等,我们理解中的一个语句。类似于语文中的一个句子一样。
var a = 1, b = 2, c =3;
当然,有人会问,console.log(1);这个算什么呢。
其实这种情况可以归为一类,单语句表达式,你既可以看作表达式,也可以看作语句,一个表达式单成一个语句。
既然分完了,我们也可以尝试这来写一下,简单点的语句分析。 比如var定义语句,或者复杂点的if语句块。
生成AST的形式可以参考这个网站,AST的一些语法可以从这个网站试出个大概
//思路分析:既然分三种情况,那么我们也从语句,表达式,单语句表达式入手,我们先定义一个方法用来分析表达式,在定义一个方法来分析语句,最后在定义一个方法分析单语句表达式。整个过程也是分为那么几步。就多了对于指针的管控。 function parse (tokens) { // 位置暂存栈,用于支持很多时候需要返回到某个之前的位置 const stashStack = []; let i = -1; // 用于标识当前遍历位置 let curToken; // 用于记录当前符号 // 暂存当前位置 function stash () { stashStack.push(i); } // 往后移动读取指针 function nextToken () { i++; curToken = tokens[i] || { type: 'EOF' };; } function parseFalse () { // 解析失败,回到上一个暂存的位置 i = stashStack.pop(); curToken = tokens[i]; } function parseSuccess () { // 解析成功,不需要再返回 stashStack.pop(); } const ast = { type: 'Program', body: [], sourceType: "script" }; // 读取下一个语句 function nextStatement () { // 暂存当前的i,如果无法找到符合条件的情况会需要回到这里 stash(); // 读取下一个符号 nextToken(); if (curToken.type === 'identifier' && curToken.value === 'if') { // 解析 if 语句 const statement = { type: 'IfStatement', }; // if 后面必须紧跟着 ( nextToken(); if (curToken.type !== 'Punctuator' || curToken.value !== '(') { throw new Error('Expected ( after if'); } // 后续的一个表达式是 if 的判断条件 statement.test = nextExpression(); // 判断条件之后必须是 ) nextToken(); if (curToken.type !== 'Punctuator' || curToken.value !== ')') { throw new Error('Expected ) after if test expression'); } // 下一个语句是 if 成立时执行的语句 statement.consequent = nextStatement(); // 如果下一个符号是 else 就说明还存在 if 不成立时的逻辑 if (curToken === 'identifier' && curToken.value === 'else') { statement.alternative = nextStatement(); } else { statement.alternative = null; } parseSuccess(); return statement; } // 如果是花括号的代码块 if (curToken.type === 'Punctuator' && curToken.value === '{') { // 以 { 开头表示是个代码块 const statement = { type: 'BlockStatement', body: [], }; while (i < tokens.length) { // 检查下一个符号是不是 } stash(); nextToken(); if (curToken.type === 'Punctuator' && curToken.value === '}') { // } 表示代码块的结尾 parseSuccess(); break; } // 还原到原来的位置,并将解析的下一个语句加到body parseFalse(); statement.body.push(nextStatement()); } // 代码块语句解析完毕,返回结果 parseSuccess(); return statement; } // 没有找到特别的语句标志,回到语句开头 parseFalse(); // 尝试解析单表达式语句 const statement = { type: 'ExpressionStatement', expression: nextExpression(), }; if (statement.expression) { nextToken(); return statement; } } // 读取下一个表达式 function nextExpression () { nextToken(); if (curToken.type === 'identifier' && curToken.value === 'var') { // 如果是定义var const variable = { type: 'VariableDeclaration', declarations: [], kind: curToken.value }; stash(); nextToken(); // 如果是分号就说明单句结束了 if(curToken.type === 'Punctuator' && curToken.value === ';') { parseSuccess(); throw new Error('error'); } else { // 循环 while (i < tokens.length) { if(curToken.type === 'identifier') { variable.declarations.id = { type: 'Identifier', name: curToken.value } } if(curToken.type === 'Punctuator' && curToken.value === '=') { nextToken(); variable.declarations.init = { type: 'Literal', name: curToken.value } } nextToken(); // 遇到;结束 if (curToken.type === 'Punctuator' && curToken.value === ';') { break; } } } parseSuccess(); return variable; } // 常量表达式 if (curToken.type === 'number' || curToken.type === 'string') { const literal = { type: 'Literal', value: eval(curToken.value), }; // 但如果下一个符号是运算符 // 此处暂不考虑多个运算衔接,或者有变量存在 stash(); nextToken(); if (curToken.type === 'operator') { parseSuccess(); return { type: 'BinaryExpression', operator: curToken.value, left: literal, right: nextExpression(), }; } parseFalse(); return literal; } if (curToken.type !== 'EOF') { throw new Error('Unexpected token ' + curToken.value); } } // 逐条解析顶层语句 while (i < tokens.length) { const statement = nextStatement(); if (!statement) { break; } ast.body.push(statement); } return ast; }
关于转换和生成,笔者还在研究,不过生成其实就是解析过程的反向,转换的话,还是挺值得深入的,因为AST这东西在好多方面用到,比如:
这篇文章讲完了,其实不理解代码没关系,把整体思路把握住就行。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
最近给团队分享了一篇babel原理,然后我把他整理成一篇blog,本篇总字数6059(含代码),速读3分钟,普通阅读5分钟,有兴趣的可以关注一下我的github博客
babel
我们来看一段代码:
经过babel之后,这段代码变成了这样:
babel的背后
babel的过程:解析——转换——生成。
这边又一个中间的东西,是抽象语法树(AST)
AST的解析过程
一个js语句是怎么被解析成AST的呢?这个中间有两个步骤,一个是分词,第二个是语义分析,怎么理解这两个东西呢?
什么叫分词?
比如我们在读一句话的时候,我们也会做分词操作,比如:“今天天气真好”,我们会把他切割成“今天”,“天气”,“真好”。
那换成js的解析器呢,我们看一下下面一个语句
console.log(1);
,js会看成console
,.
,log
,(
,1
,)
,;
。所以我们可以把js解析器能识别的最小词法单元。
当然这样的分词器我们可以简易实现一下。
语义分析的话就比较难了,为什么这么说呢?
因为这个不像分词这样有个标准,有些东西都要靠自己去摸索。
其实语义分析分为两块,一块是语句,还有一块是表达式。
什么叫语句?什么叫表达式呢?
表达式,比如:
a > b; a + b;
这一类的,可以嵌套,也可以运用在语句中。语句,比如:
var a = 1, b = 2, c =3;
等,我们理解中的一个语句。类似于语文中的一个句子一样。当然,有人会问,
console.log(1);
这个算什么呢。其实这种情况可以归为一类,单语句表达式,你既可以看作表达式,也可以看作语句,一个表达式单成一个语句。
既然分完了,我们也可以尝试这来写一下,简单点的语句分析。
比如var定义语句,或者复杂点的if语句块。
生成AST的形式可以参考这个网站,AST的一些语法可以从这个网站试出个大概
关于转换和生成,笔者还在研究,不过生成其实就是解析过程的反向,转换的话,还是挺值得深入的,因为AST这东西在好多方面用到,比如:
这篇文章讲完了,其实不理解代码没关系,把整体思路把握住就行。
The text was updated successfully, but these errors were encountered: