在没风的地方找太阳  在你冷的地方做暖阳 人事纷纷  你总太天真  往后的余生  我只要你 往后余生  风雪是你  平淡是你  清贫也是你 荣华是你  心底温柔是你  目光所致  也是你 想带你去看晴空万里  想大声告诉你我为你着迷 往事匆匆  你总会被感动  往后的余生  我只要你 往后余生  冬雪是你  春花是你  夏雨也是你 秋黄是你  四季冷暖是你  目光所致  也是你 往后余生  风雪是你  平淡是你  清贫也是你 荣华是你  心底温柔是你  目光所致  也是你
jQuery火箭图标返回顶部代码 - 站长素材

Babel是如何读懂JS代码的

Babel是如何编译JS代码的及理解抽象语法树(AST)

1. Babel的作用是?
   很多浏览器目前还不支持ES6的代码,但是我们可以通过Babel将ES6的代码转译成ES5代码,让所有的浏览器都能理解的代码,这就是Babel的作用。
2. Babel是如何工作的?
   Babel的编译过程和大多数其他语言的编译器大致相同,可以分为三个阶段。
1. 解析(PARSE):将代码字符串解析成抽象语法树。
2. 转换(TRANSFORM):对抽象语法树进行转换操作。
3. 生成(GENERATE): 根据变换后的抽象语法树再生成代码字符串。
比如我们在 .babelrc里配置的presets和plugins是在第二步进行的。
我们可以看一下下面的流程图就可以很清晰了:

3. 什么是抽象语法树(AST)?
   我们知道javascript程序一般是由一系列的字符组成的,每一个字符都有一些含义,比如我们可以使用匹配的字符([], {}, ()), 或一些其他成对的字符('', "")和代码缩进让程序解析更加简单,但是对计算机并不适用,这些字符在内存中仅仅是个数值,但是计算机并不知道一个程序内部有多少个变量这些高级问题,
这个时候我们需要寻找一些能让计算机理解的方式,这个时候,抽象语法树诞生了。

4. 抽象语法树是如何产生的?
我们通过上面知道,Babel的工作的第一步是 解析操作,将代码字符串解析成抽象语法树,那么抽象语法树就是在解析过程中产生的。其实解析又可以分成两个
步骤:
4-1 分词: 将整个代码字符串分割成 语法单元数组。
4-2 语义分析:在分词结果的基础之上分析 语法单元之间的关系。

分词:
  先来理解一下什么是语法单元? 语法单元是被解析语法当中具备实际意义的最小单元,简单的来理解就是自然语言中的词语。
比如我们来看下面的一句话:
2022年亚运会将在杭州举行,下面我们可以把这句话拆分成最小单元:2022年, 亚运会, 将, 在, 杭州, 举行。这就是我们所说的分词。也是最小单元,
如果我们把它再拆分出去的话,那就没有什么实际意义了。

那么JS代码中有哪些语法单元呢?大致有下面这些:
1. 空白。JS中连续的空格,换行,缩进等这些如果不在字符串里面,就没有任何实际的意义,因此我们可以将连续的空白组合在一起作为一个语法单元。
2. 注释。行注释或块注释,对于编写人或维护人注释是有意义的,但是对于计算机来说知道这是个注释就可以了,并不关心注释的含义,因此我们可以将
注释理解为一个不可拆分的语法单元。
3. 字符串。对计算机而言,字符串的内容会参与计算或显示,因此有可以为一个语法单元。
4. 数字。JS中有16,10,8进制以及科学表达式等语法,因此数字也可以理解一个语法单元。
5. 标识符。没有被引号括起来的连续字符,可包含字母 _, $ 及数字,或 true, false等这些内置常量,或 if,return,function等这些关键字。
6. 运算符: +, -, *, /, >, < 等。
7,还有一些其他的,比如括号,中括号,大括号,分号,冒号,点等等。

下面我们来看看代码内是如何分词的?
比如如下代码:

if (1 > 0) {
  alert("aa");
}

我们希望得到的分词是如下:

'if' ' ' '(' '1' ' ' '>' ' ' '0' )' ' ' '{' '\n ' 'alert' '(' "aa" ')' ";" '\n' '}'

下面我们就来一个个字符进行遍历,然后分情况判断,如下代码:

复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>分词</title>
  </head>
  <body>
    <script>
      function tokenizeCode(code) {
        var tokens = [];  // 保存结果数组
        for (var i = 0; i < code.length; i++) {
          // 从0开始 一个个字符读取
          var currentChar = code.charAt(i);
          if (currentChar === ';') {
            tokens.push({
              type: 'sep',
              value: currentChar
            });
            // 该字符已经得到解析了,直接循环下一个
            continue;
          }
          if (currentChar === '(' || currentChar === ')') {
            tokens.push({
              type: 'parens',
              value: currentChar
            });
            continue;
          }
          if (currentChar === '{' || currentChar === '}') {
            tokens.push({
              type: 'brace',
              value: currentChar
            });
            continue;
          }
          if (currentChar === '>' || currentChar === '<') {
            tokens.push({
              type: 'operator',
              value: currentChar
            });
            continue;
          }
          if (currentChar === '"' || currentChar === '\'') {
            // 如果是单引号或双引号,表示一个字符的开始
            var token = {
              type: 'string',
              value: currentChar
            };
            tokens.push(token);
            var closer = currentChar;

            // 表示下一个字符是不是被转译了
            var escaped = false;
            // 循环遍历 寻找字符串的末尾
            for(i++; i < code.length; i++) {
              currentChar = code.charAt(i);
              // 将当前遍历到的字符先加到字符串内容中
              token.value += currentChar;
              if (escaped) {
                // 如果当前为true的话,就变为false,然后该字符就不做特殊的处理
                escaped = false;
              } else if (currentChar === '\\') {
                // 如果当前的字符是 \, 将转译状态变为true,下一个字符不会被做处理
                escaped = true;
              } else if (currentChar === closer) {
                break;
              }
            }
            continue;
          }

          // 数字做处理 
          if (/[0-9]/.test(currentChar)) {
            // 如果数字是以 0 到 9的字符开始的话
            var token = {
              type: 'number',
              value: currentChar
            };
            tokens.push(token);
            // 继续遍历,如果下一个字符还是数字的话,比如0到9或小数点的话
            for (i++; i < code.length; i++) {
              currentChar = code.charAt(i);
              if (/[0-9\.]/.test(currentChar)) {
                // 先不考虑多个小数点 或 进制的情况下
                token.value += currentChar;
              } else {
                // 如果下一个字符不是数字的话,需要把i值返回原来的位置上,需要减1
                i--;
                break;
              }
            }
            continue;
          }
          // 标识符是以字母,$, _开始的 做判断
          if (/[a-zA-Z\$\_]/.test(currentChar)) {
            var 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)) {
            var token = {
              type: 'whitespace',
              value: currentChar
            }
            tokens.push(token);
            // 继续遍历下一个字符
            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;
      } 
      var tokens = tokenizeCode(`
        if (1 > 0) {
          alert("aa");
        }
      `);
      console.log(tokens);
    </script>
  </body>
</html>
复制代码

打印的结果如下:

复制代码
/*
  [
    {type: "whitespace", value: "\n"},
    {type: "identifier", value: "if"},
    {type: "whitespace", value: " "},
    {type: "parens", value: "("},
    {type: "number", value: "1"},
    {type: "whitespace", value: " "},
    {type: "operator", value: ">"},
    {type: "whitespace", value: " "},
    {type: "number", value: "0"},
    {type: "parens", value: ")"},
    {type: "whitespace", value: " "},
    {type: "brace", value: "{"},
    {type: "whitespace", value: "\n"},
    {type: "identifier", value: "alert"},
    {type: "parens", value: "("},
    {type: "string", value: "'aa'"},
    {type: "parens", value: ")"},
    {type: "sep", value: ";"},
    {type: "whitespace", value: "\n"},
    {type: "brace", value: "}"},
    {type: "whitespace", value: "\n"}
  ]
*/
复制代码

语义分析:

语义分析是把词汇进行立体的组合,确定有多重意义的词语最终是什么意思,多个词语之间有什么关系以及又如何在什么地方断句等等。我们对上面的输出代码再进行语义分析了,请看如下代码:

复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>分词</title>
  </head>
  <body>
    <script>
      var parse = function(tokens) {
        let i = -1;     // 用于标识当前遍历位置
        let curToken;   // 用于记录当前符号
        // 读取下一个语句
        function nextStatement () {

          // 暂存当前的i,如果无法找到符合条件的情况会需要回到这里
          stash();
          
          // 读取下一个符号
          nextToken();
          if (curToken.type === 'identifier' && curToken.value === 'if') {
            // 解析 if 语句
            const statement = {
              type: 'IfStatement',
            };
            // if 后面必须紧跟着 (
            nextToken();
            if (curToken.type !== 'parens' || curToken.value !== '(') {
              throw new Error('Expected ( after if');
            }

            // 后续的一个表达式是 if 的判断条件
            statement.test = nextExpression();

            // 判断条件之后必须是 )
            nextToken();
            if (curToken.type !== 'parens' || 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;
            }
            commit();
            return statement;
          }

          if (curToken.type === 'brace' && curToken.value === '{') {
            // 以 { 开头表示是个代码块,我们暂不考虑JSON语法的存在
            const statement = {
              type: 'BlockStatement',
              body: [],
            };
            while (i < tokens.length) {
              // 检查下一个符号是不是 }
              stash();
              nextToken();
              if (curToken.type === 'brace' && curToken.value === '}') {
                // } 表示代码块的结尾
                commit();
                break;
              }
              // 还原到原来的位置,并将解析的下一个语句加到body
              rewind();
              statement.body.push(nextStatement());
            }
            // 代码块语句解析完毕,返回结果
            commit();
            return statement;
          }
          
          // 没有找到特别的语句标志,回到语句开头
          rewind();

          // 尝试解析单表达式语句
          const statement = {
            type: 'ExpressionStatement',
            expression: nextExpression(),
          };
          if (statement.expression) {
            nextToken();
            if (curToken.type !== 'EOF' && curToken.type !== 'sep') {
              throw new Error('Missing ; at end of expression');
            }
            return statement;
          }
        }
        // 读取下一个表达式
        function nextExpression () {
          nextToken();
          if (curToken.type === 'identifier') {
            const identifier = {
              type: 'Identifier',
              name: curToken.value,
            };
            stash();
            nextToken();
            if (curToken.type === 'parens' && curToken.value === '(') {
              // 如果一个标识符后面紧跟着 ( ,说明是个函数调用表达式
              const expr = {
                type: 'CallExpression',
                caller: identifier,
                arguments: [],
              };

              stash();
              nextToken();
              if (curToken.type === 'parens' && curToken.value === ')') {
                // 如果下一个符合直接就是 ) ,说明没有参数
                commit();
              } else {
                // 读取函数调用参数
                rewind();
                while (i < tokens.length) {
                  // 将下一个表达式加到arguments当中
                  expr.arguments.push(nextExpression());
                  nextToken();
                  // 遇到 ) 结束
                  if (curToken.type === 'parens' && curToken.value === ')') {
                    break;
                  }
                  // 参数间必须以 , 相间隔
                  if (curToken.type !== 'comma' && curToken.value !== ',') {
                    throw new Error('Expected , between arguments');
                  }
                }
              }
              commit();
              return expr;
            }
            rewind();
            return identifier;
          }
          if (curToken.type === 'number' || curToken.type === 'string') {
            // 数字或字符串,说明此处是个常量表达式
            const literal = {
              type: 'Literal',
              value: eval(curToken.value),
            };
            // 但如果下一个符号是运算符,那么这就是个双元运算表达式
            stash();
            nextToken();
            if (curToken.type === 'operator') {
              commit();
              return {
                type: 'BinaryExpression',
                left: literal,
                right: nextExpression(),
              };
            }
            rewind();
            return literal;
          }
          if (curToken.type !== 'EOF') {
            throw new Error('Unexpected token ' + curToken.value);
          }
        }
        // 往后移动读取指针,自动跳过空白
        function nextToken () {
          do {
            i++;
            curToken = tokens[i] || { type: 'EOF' };
          } while (curToken.type === 'whitespace');
        }
        // 位置暂存栈,用于支持很多时候需要返回到某个之前的位置
        const stashStack = [];
        function stash () {
          // 暂存当前位置
          stashStack.push(i);
        }
        function rewind () {
          // 解析失败,回到上一个暂存的位置
          i = stashStack.pop();
          curToken = tokens[i];
        }
        function commit () {
          // 解析成功,不需要再返回
          stashStack.pop();
        }
        const ast = {
          type: 'Program',
          body: [],
        };
        // 逐条解析顶层语句
        while (i < tokens.length) {
          const statement = nextStatement();
          if (!statement) {
            break;
          }
          ast.body.push(statement);
        }
        return ast;
      };
      var ast = parse([
          {type: "whitespace", value: "\n"},
          {type: "identifier", value: "if"},
          {type: "whitespace", value: " "},
          {type: "parens", value: "("},
          {type: "number", value: "1"},
          {type: "whitespace", value: " "},
          {type: "operator", value: ">"},
          {type: "whitespace", value: " "},
          {type: "number", value: "0"},
          {type: "parens", value: ")"},
          {type: "whitespace", value: " "},
          {type: "brace", value: "{"},
          {type: "whitespace", value: "\n"},
          {type: "identifier", value: "alert"},
          {type: "parens", value: "("},
          {type: "string", value: "'aa'"},
          {type: "parens", value: ")"},
          {type: "sep", value: ";"},
          {type: "whitespace", value: "\n"},
          {type: "brace", value: "}"},
          {type: "whitespace", value: "\n"}
                ]);
      console.log(ast);
    </script>
  </body>
</html>
复制代码

最后输出ast值为如下:

复制代码
{
  "type": "Program",
  "body": [
    {
      "type": "IfStatement",
      "test": {
        "type": "BinaryExpression",
        "left": {
          "type": "Literal",
          "value": 1
        },
        "right": {
          "type": "Literal",
          "value": 0
        }
      },
      "consequent": {
        "type": "BlockStatement",
        "body": [
          {
            "type": "ExpressionStatement",
            "expression": {
              "type": "CallExpression",
              "caller": {
                "type": "Identifier",
                "value": "alert"
              },
              "arguments": [
                {
                  "type": "Literal",
                  "value": "aa"
                }
              ]
            }
          }
        ]
      },
      "alternative": null
    }
  ]
}
复制代码

我们现在再来分析下上面代码的含义:分析如下:

第一步调用parse该方法,传入参数分词中输出的结果,代码如下:

复制代码
var ast = parse([
  {type: "whitespace", value: "\n"},
  {type: "identifier", value: "if"},
  {type: "whitespace", value: " "},
  {type: "parens", value: "("},
  {type: "number", value: "1"},
  {type: "whitespace", value: " "},
  {type: "operator", value: ">"},
  {type: "whitespace", value: " "},
  {type: "number", value: "0"},
  {type: "parens", value: ")"},
  {type: "whitespace", value: " "},
  {type: "brace", value: "{"},
  {type: "whitespace", value: "\n"},
  {type: "identifier", value: "alert"},
  {type: "parens", value: "("},
  {type: "string", value: "'aa'"},
  {type: "parens", value: ")"},
  {type: "sep", value: ";"},
  {type: "whitespace", value: "\n"},
  {type: "brace", value: "}"},
  {type: "whitespace", value: "\n"}
]);
复制代码

先初始化如下参数:
let i = -1; // 用于标识当前遍历位置
let curToken; // 用于记录当前符号

function nextStatement() {
// ... 很多代码
}
function nextExpression() {
// ... 很多代码
}
function nextToken() {
// ... 很多代码
}
// 位置暂存栈,用于支持很多时候需要返回到某个之前的位置
const stashStack = [];

function rewind () {
// ... 很多代码
}
function commit () {
// ... 很多代码
}
真正初始化的代码如下:

复制代码
const ast = {
  type: 'Program',
  body: [],
};
// 逐条解析顶层语句
while (i < tokens.length) {
  const statement = nextStatement();
  if (!statement) {
    break;
  }
  ast.body.push(statement);
}
return ast;
复制代码

先定义ast对象,最顶层的类型为 Program, body为[], 然后依次循环tokens的长度,第一步调用 nextStatement()方法,在该方法内部,先是
存储当前的i值,代码如下:
// 暂存当前的i,如果无法找到符合条件的情况会需要回到这里
stash();

function stash () {
// 暂存当前位置
stashStack.push(i);
}
因此 var stashStack = [-1]了;
接着 调用 nextToken();方法 读取下一个符号,nextToken代码如下:
// 往后移动读取指针,自动跳过空白
function nextToken () {
do {
i++;
curToken = tokens[i] || { type: 'EOF' };
} while (curToken.type === 'whitespace');
}
上面使用到do,while语句,该代码的含义是先执行一次,然后再判断条件是否符合要求,因此此时 i = 0 了,因此 curToken的值变为如下:
var curToken = {type: "whitespace", value: "\n"}; 然后while语句在判断 curToken.type === 'whitespace' 是否相等,
很明显是相等的,因此i++; 然后 var curToken = {type: "identifier", value: "if"}; 这个值了;然后再判断该type是否等于?
可以看到不等于,因此curToken的值就是如上代码的。

然后 就是if语句代码判断如下:

复制代码
if (curToken.type === 'identifier' && curToken.value === 'if') {
  // 解析 if 语句
  const statement = {
    type: 'IfStatement',
  };
  // if 后面必须紧跟着 (
  nextToken();
  if (curToken.type !== 'parens' || curToken.value !== '(') {
    throw new Error('Expected ( after if');
  }

  // 后续的一个表达式是 if 的判断条件
  statement.test = nextExpression();

  // 判断条件之后必须是 )
  nextToken();
  if (curToken.type !== 'parens' || 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;
  }
  commit();
  return statement;
}
复制代码

var curToken = {type: "identifier", value: "if"}; 因此满足if条件判断语句,定义 statement对象如下:
const statement = {
   type: 'IfStatement'
};
调用 nextToken()方法 读取下一个字符,因此先执行一次代码, var curToken = {type: "whitespace", value: " "}; 然后再判断while条件,
最后curToken的值变为如下: var curToken = {type: "parens", value: "("}; 所以if语句后面紧跟着( 是正常的,然后就是需要判断if语句的
表达式了;如上代码:
// 后续的一个表达式是 if 的判断条件
statement.test = nextExpression();

// 判断条件之后必须是 )
nextToken();
if (curToken.type !== 'parens' || curToken.value !== ')') {
throw new Error('Expected ) after if test expression');
}
先是调用 nextExpression 方法,代码如下:

复制代码
// 读取下一个表达式
function nextExpression () {
  nextToken();
  if (curToken.type === 'identifier') {
    const identifier = {
      type: 'Identifier',
      name: curToken.value,
    };
    stash();
    nextToken();
    if (curToken.type === 'parens' && curToken.value === '(') {
      // 如果一个标识符后面紧跟着 ( ,说明是个函数调用表达式
      const expr = {
        type: 'CallExpression',
        caller: identifier,
        arguments: [],
      };

      stash();
      nextToken();
      if (curToken.type === 'parens' && curToken.value === ')') {
        // 如果下一个符合直接就是 ) ,说明没有参数
        commit();
      } else {
        // 读取函数调用参数
        rewind();
        while (i < tokens.length) {
          // 将下一个表达式加到arguments当中
          expr.arguments.push(nextExpression());
          nextToken();
          // 遇到 ) 结束
          if (curToken.type === 'parens' && curToken.value === ')') {
            break;
          }
          // 参数间必须以 , 相间隔
          if (curToken.type !== 'comma' && curToken.value !== ',') {
            throw new Error('Expected , between arguments');
          }
        }
      }
      commit();
      return expr;
    }
    rewind();
    return identifier;
  }
  if (curToken.type === 'number' || curToken.type === 'string') {
    // 数字或字符串,说明此处是个常量表达式
    const literal = {
      type: 'Literal',
      value: eval(curToken.value),
    };
    // 但如果下一个符号是运算符,那么这就是个双元运算表达式
    stash();
    nextToken();
    if (curToken.type === 'operator') {
      commit();
      return {
        type: 'BinaryExpression',
        left: literal,
        right: nextExpression(),
      };
    }
    rewind();
    return literal;
  }
  if (curToken.type !== 'EOF') {
    throw new Error('Unexpected token ' + curToken.value);
  }
}
复制代码

在代码内部调用 nextToken方法,curToken的值变为 var curToken = {type: "number", value: "1"};
所以满足上面的第二个if条件语句了,所以先定义 literal的值,如下:
const literal = {
type: 'Literal',
value: eval(curToken.value),
};
所以
const literal = {
type: 'Literal',
value: 1,
};
然后调用 stash()方法保存当前的的值;
const stashStack = [];
function stash () {
// 暂存当前位置
stashStack.push(i);
}
因此stashStack的值变为 const stashStack = [-1, 4]; 接着调用 nextToken()方法,因此此时的curToken的值变为如下:
var curToken = {type: "operator", value: ">"}; 所以它满足 上面代码的 if (curToken.type === 'operator') { 这个条件,
因此 会返回
return {
type: 'BinaryExpression',
left: {
type: 'Literal',
value: 1
},
right: nextExpression(),
};
right的值 使用递归的方式重新调用 nextExpression 函数。且在返回之前调用了 commit()函数,该函数代码如下:
function commit () {
// 解析成功,不需要再返回
stashStack.pop();
}
如上函数使用 数组的pop方法,删除数组的最后一个元素,因此此时的 stashStack 的值变为 const stashStack = [-1];
如上代码,刚刚i = 4的时候,再调用 nextToken()方法,因此此时i就等于6了,递归调用 nextExpression方法后,再调用nextToken();方法,
因此此时 i 的值变为8,因此 curToken的值变为如下;var curToken = {type: "number", value: "0"}; 和上面一样,还是进入了第二个if
语句代码内;此时literal的值变为如下:
const literal = {
type: 'Literal',
value: 0
};
stash(); 调用该方法后,因此 var stashStack = [-1, 8]了,再调用 nextToken(); 方法后,此时 curToken = {type: "parens", value: ")"}; 下面的if语句不满足,直接调用 rewind()方法; 然后返回 return literal;的值;

rewind方法如下代码:
function rewind () {
// 解析失败,回到上一个暂存的位置
i = stashStack.pop();
curToken = tokens[i];
};
我们之前保存的stashStack的值为 [-1, 8]; 因此使用pop方法后,或者i的值为8,因此curToken = {type: "number", value: "0"} 了;
最后就返回成这样的;
return {
type: 'BinaryExpression',
left: {
type: 'Literal',
value: 1
},
right: {
type: 'Literal',
value: 0
}
};
因此 statement.test = {
type: 'BinaryExpression',
left: {
type: 'Literal',
value: 1
},
right: {
type: 'Literal',
value: 0
}
}
我们接着看 nextStatement 语句中的如下代码;
// 下一个语句是 if 成立时执行的语句
statement.consequent = nextStatement();
又递归调用该方法了,因此之前( 的位置是9,因此此时再循环调用,i的值变为11了,因此 curToken = {type: "brace", value: "{"};
所以就进入了第二个if语句的判断条件了,如下: if (curToken.type === 'brace' && curToken.value === '{') {
先定义statement的值如下:
// 以 { 开头表示是个代码块
const statement = {
type: 'BlockStatement',
body: [],
};
while (i < tokens.length) {
// 检查下一个符号是不是 }
stash();
nextToken();
if (curToken.type === 'brace' && curToken.value === '}') {
// } 表示代码块的结尾
commit();
break;
}
// 还原到原来的位置,并将解析的下一个语句加到body
rewind();
statement.body.push(nextStatement());
}
// 代码块语句解析完毕,返回结果
commit();
return statement;

代码如上, 此时i = 11; 进入while循环语句了,调用 stash保存当前的值 因此 var stashStack = [-1, 11]; 调用 nextToken方法后,那么
curToken = {type: "identifier", value: "alert"}; while代码不满足要求,因此 调用 rewind()方法返回到 i = 11位置上了,然后继续
调用nextStatement方法,把返回后的结果 放入 statement.body数组内,调用 nextToken(); 方法后,回到13位置上了,因此此时
var curToken = {type: "identifier", value: "alert"}; 上面的if条件语句都不满足,所以定义如下变量了。
// 尝试解析单表达式语句
const statement = {
type: 'ExpressionStatement',
expression: nextExpression(),
};
调用 nextExpression 该方法,该方法如下:

复制代码
function nextExpression () {
  nextToken();
  if (curToken.type === 'identifier') {
    const identifier = {
      type: 'Identifier',
      name: curToken.value,
    };
    stash();
    nextToken();
    if (curToken.type === 'parens' && curToken.value === '(') {
      // 如果一个标识符后面紧跟着 ( ,说明是个函数调用表达式
      const expr = {
        type: 'CallExpression',
        caller: identifier,
        arguments: [],
      };

      stash();
      nextToken();
      if (curToken.type === 'parens' && curToken.value === ')') {
        // 如果下一个符合直接就是 ) ,说明没有参数
        commit();
      } else {
        // 读取函数调用参数
        rewind();
        while (i < tokens.length) {
          // 将下一个表达式加到arguments当中
          expr.arguments.push(nextExpression());
          nextToken();
          // 遇到 ) 结束
          if (curToken.type === 'parens' && curToken.value === ')') {
            break;
          }
          // 参数间必须以 , 相间隔
          if (curToken.type !== 'comma' && curToken.value !== ',') {
            throw new Error('Expected , between arguments');
          }
        }
      }
      commit();
      return expr;
    }
    rewind();
    return identifier;
  }
  if (curToken.type === 'number' || curToken.type === 'string') {
    // 数字或字符串,说明此处是个常量表达式
    const literal = {
      type: 'Literal',
      value: eval(curToken.value),
    };
    // 但如果下一个符号是运算符,那么这就是个双元运算表达式
    stash();
    nextToken();
    if (curToken.type === 'operator') {
      commit();
      return {
        type: 'BinaryExpression',
        left: literal,
        right: nextExpression(),
      };
    }
    rewind();
    return literal;
  }
  if (curToken.type !== 'EOF') {
    throw new Error('Unexpected token ' + curToken.value);
  }
}
复制代码

如上 curToken的值 curToken = {type: "identifier", value: "alert"}; 因此会进入第一个if语句内,identifier的值变为如下:
const identifier = {
type: 'Identifier',
name: alert,
};
调用 stash()方法,此时 stashStack 的值变为 var stashStack = [-1, 13]; 再接着调用 nextToken方法, 因此curToken的值变为如下:
var curToken = {type: "parens", value: "("},因此会进入if条件语句了,如下:
if (curToken.type === 'parens' && curToken.value === '(') {; 的条件判断了;接着定义expr的变量如下代码:
// 如果一个标识符后面紧跟着 ( ,说明是个函数调用表达式
const expr = {
type: 'CallExpression',
caller: identifier,
arguments: [],
};
再调用该方法后,stash(); 此时 stashStack的值变为 [-1, 14], 再调用 nextToken(); 方法后,此时 curToken的值变为如下:
var curToken = {type: "string", value: "'aa'"}; 再接着执行 if (curToken.type === 'parens' && curToken.value === '(')
代码么有找到条件判断,因此在调用 rewind(); 返回再返回14的位置上,此时 curToken = {type: "parens", value: "("};
因此执行后,紧着如下代码:
// 读取函数调用参数
rewind();
while (i < tokens.length) {
// 将下一个表达式加到arguments当中
expr.arguments.push(nextExpression());
nextToken();
// 遇到 ) 结束
if (curToken.type === 'parens' && curToken.value === ')') {
break;
}
// 参数间必须以 , 相间隔
if (curToken.type !== 'comma' && curToken.value !== ',') {
throw new Error('Expected , between arguments');
}
}
, 原理还是和上面一样,这里不一一解析了,太烦了;大家可以自己去理解了。

以上就是语义解析的部分主要思路。

详情可以看github官网的代码中文翻译的

posted @ 2021-11-22 12:36  艺术诗人  阅读(105)  评论(0编辑  收藏  举报