块语句与嵌套作用域

在上一篇文章中,我们已经添加了对语句的解析。目前仅支持表达式语句。在本篇文章中,我们添加对 块语句 的解析。

块语句

块语句,在 C、Java 等语言中,由一对大括号包围。我们此处也采用这种形式。块语句用于封装函数体,也用于定义 if 等控制流语句。在大括号内部定义的变量,其作用域被限制在这个块中。

因为我们现在是测试驱动开发,所以我们先写测试用例。如代码清单 1 所示,我们针对块语句,添加 BlockStatement。块语句的内容是多条语句,即上篇文章中实现的 StatementList。

代码清单 1 块语句 AST
  1. module.exports = test => {
  2.     test(`
  3.  
  4.     {
  5.         42;
  6.  
  7.         "hello";
  8.     }
  9.  
  10.     `, {
  11.         type: 'Program',
  12.         body: [
  13.             {
  14.                 type: 'BlockStatement',
  15.                 body: [
  16.                     {
  17.                         type: 'ExpressionStatement',
  18.                         expression: {
  19.                             type: 'NumericLiteral',
  20.                             value: 42,
  21.                         }
  22.                     },
  23.                     {
  24.                         type: 'ExpressionStatement',
  25.                         expression: {
  26.                             type: 'StringLiteral',
  27.                             value: 'hello',
  28.                         }
  29.                     }
  30.                 ]
  31.             }
  32.         ]
  33.     });
  34. };

写好测试用例后,我们开始实现。如代码清单 2 所示,我们新增块语句解析,通过判断 lookahead 是否为左大括号。

代码清单 2 Statement
  1.     /**
  2.      * Statement
  3.      *      : ExpressionStatement
  4.      *      | BlockStatement
  5.      *      | EmptyStatement
  6.      *      ;
  7.      */
  8.     Statement() {
  9.         switch (this._lookahead.type) {
  10.             case '{':
  11.                 return this.BlockStatement();
  12.             default:
  13.                 return this.ExpressionStatement();
  14.         }
  15.     }

块语句的具体解析,如代码清单 3 所示,它的文法是“左大括号 + 多条语句 + 右大括号”。所以我们可以复用之前的 StatementList 逻辑,同时“吃掉”左、右大括号。

代码清单 3 BlockStatement
  1.     /**
  2.      * BlockStatement
  3.      *      : '{' OptStatementList '}'
  4.      *      ;
  5.      */
  6.     BlockStatement() {
  7.         this._eat('{');
  8.  
  9.         const body = this._lookahead.type !== '}' ? this.StatementList('}') : [];
  10.  
  11.         this._eat('}');
  12.  
  13.         return {
  14.             type: 'BlockStatement',
  15.             body,
  16.         }
  17.     }

注意,如代码清单 4 所示,StatementList 需要改动。因为现在解析块语句中的各条语句,不是一直解析到末尾,而是解析到右大括号为止。所以,我们新增参数用于指定结束 token。并设置其缺省值为末尾,从而不影响现有逻辑。

代码清单 4 StatementList
  1.     /**
  2.      * StatementList
  3.      *      : Statement
  4.      *      | StatementList Statement
  5.      *      ;
  6.      */
  7.     StatementList(stopLookahead = null) {
  8.         const statementList = [this.Statement()];
  9.  
  10.         while (this._lookahead != null && this._lookahead.type !== stopLookahead) {
  11.             statementList.push(this.Statement());
  12.         }
  13.         return statementList;
  14.     }

这时候,块语句就实现好了,可以运行确认我们的测试用例。从实现中我们可以看到,块语句包含各条语句,而语句也能是块语句,已经是天然的嵌套了。所以,我们编写如代码清单 5 所示的测试用例,块语句里嵌套块语句。运行可以看到,目前已经“天然”就支持了。

代码清单 5 嵌套
  1. module.exports = test => {
  2.     test(`
  3.  
  4.     {
  5.         42;
  6.         {
  7.             "hello";
  8.         }
  9.     }
  10.  
  11.     `, {
  12.         type: 'Program',
  13.         body: [
  14.             {
  15.                 type: 'BlockStatement',
  16.                 body: [
  17.                     {
  18.                         type: 'ExpressionStatement',
  19.                         expression: {
  20.                             type: 'NumericLiteral',
  21.                             value: 42,
  22.                         }
  23.                     },
  24.                     {
  25.                         type: 'BlockStatement',
  26.                         body: [
  27.                             {
  28.                                 type: 'ExpressionStatement',
  29.                                 expression: {
  30.                                     type: 'StringLiteral',
  31.                                     value: 'hello',
  32.                                 }
  33.                             }
  34.                         ]
  35.                     }
  36.                 ]
  37.             }
  38.         ]
  39.     });
  40. };

空语句

空语句很简单,就是单独的一个分号。所以就不单独弄一篇文章了,把它放在这边顺带实现了。如代码清单 5 所示,就是单独的一个分号。

代码清单 5 空语句
  1.     /**
  2.      * Statement
  3.      *      : ExpressionStatement
  4.      *      | BlockStatement
  5.      *      | EmptyStatement
  6.      *      ;
  7.      */
  8.     Statement() {
  9.         switch (this._lookahead.type) {
  10.             case ';':
  11.                 return this.EmptyStatement();
  12.             case '{':
  13.                 return this.BlockStatement();
  14.             default:
  15.                 return this.ExpressionStatement();
  16.         }
  17. }
  18.  
  19.     /**
  20.      * EmptyStatement
  21.      *      : ';'
  22.      *      ;
  23.      */
  24.     EmptyStatement() {
  25.         this._eat(';');
  26.  
  27.         return {
  28.             type: 'EmptyStatement'
  29.         }
  30.     }