块语句与嵌套作用域
在上一篇文章中,我们已经添加了对语句的解析。目前仅支持表达式语句。在本篇文章中,我们添加对 块语句 的解析。
块语句
块语句,在 C、Java 等语言中,由一对大括号包围。我们此处也采用这种形式。块语句用于封装函数体,也用于定义 if 等控制流语句。在大括号内部定义的变量,其作用域被限制在这个块中。
因为我们现在是测试驱动开发,所以我们先写测试用例。如代码清单 1 所示,我们针对块语句,添加 BlockStatement。块语句的内容是多条语句,即上篇文章中实现的 StatementList。
- module.exports = test => {
 - test(`
 - {
 - 42;
 - "hello";
 - }
 - `, {
 - type: 'Program',
 - body: [
 - {
 - type: 'BlockStatement',
 - body: [
 - {
 - type: 'ExpressionStatement',
 - expression: {
 - type: 'NumericLiteral',
 - value: 42,
 - }
 - },
 - {
 - type: 'ExpressionStatement',
 - expression: {
 - type: 'StringLiteral',
 - value: 'hello',
 - }
 - }
 - ]
 - }
 - ]
 - });
 - };
 
写好测试用例后,我们开始实现。如代码清单 2 所示,我们新增块语句解析,通过判断 lookahead 是否为左大括号。
- /**
 - * Statement
 - * : ExpressionStatement
 - * | BlockStatement
 - * | EmptyStatement
 - * ;
 - */
 - Statement() {
 - switch (this._lookahead.type) {
 - case '{':
 - return this.BlockStatement();
 - default:
 - return this.ExpressionStatement();
 - }
 - }
 
块语句的具体解析,如代码清单 3 所示,它的文法是“左大括号 + 多条语句 + 右大括号”。所以我们可以复用之前的 StatementList 逻辑,同时“吃掉”左、右大括号。
- /**
 - * BlockStatement
 - * : '{' OptStatementList '}'
 - * ;
 - */
 - BlockStatement() {
 - this._eat('{');
 - const body = this._lookahead.type !== '}' ? this.StatementList('}') : [];
 - this._eat('}');
 - return {
 - type: 'BlockStatement',
 - body,
 - }
 - }
 
注意,如代码清单 4 所示,StatementList 需要改动。因为现在解析块语句中的各条语句,不是一直解析到末尾,而是解析到右大括号为止。所以,我们新增参数用于指定结束 token。并设置其缺省值为末尾,从而不影响现有逻辑。
- /**
 - * StatementList
 - * : Statement
 - * | StatementList Statement
 - * ;
 - */
 - StatementList(stopLookahead = null) {
 - const statementList = [this.Statement()];
 - while (this._lookahead != null && this._lookahead.type !== stopLookahead) {
 - statementList.push(this.Statement());
 - }
 - return statementList;
 - }
 
这时候,块语句就实现好了,可以运行确认我们的测试用例。从实现中我们可以看到,块语句包含各条语句,而语句也能是块语句,已经是天然的嵌套了。所以,我们编写如代码清单 5 所示的测试用例,块语句里嵌套块语句。运行可以看到,目前已经“天然”就支持了。
- module.exports = test => {
 - test(`
 - {
 - 42;
 - {
 - "hello";
 - }
 - }
 - `, {
 - type: 'Program',
 - body: [
 - {
 - type: 'BlockStatement',
 - body: [
 - {
 - type: 'ExpressionStatement',
 - expression: {
 - type: 'NumericLiteral',
 - value: 42,
 - }
 - },
 - {
 - type: 'BlockStatement',
 - body: [
 - {
 - type: 'ExpressionStatement',
 - expression: {
 - type: 'StringLiteral',
 - value: 'hello',
 - }
 - }
 - ]
 - }
 - ]
 - }
 - ]
 - });
 - };
 
空语句
空语句很简单,就是单独的一个分号。所以就不单独弄一篇文章了,把它放在这边顺带实现了。如代码清单 5 所示,就是单独的一个分号。
- /**
 - * Statement
 - * : ExpressionStatement
 - * | BlockStatement
 - * | EmptyStatement
 - * ;
 - */
 - Statement() {
 - switch (this._lookahead.type) {
 - case ';':
 - return this.EmptyStatement();
 - case '{':
 - return this.BlockStatement();
 - default:
 - return this.ExpressionStatement();
 - }
 - }
 - /**
 - * EmptyStatement
 - * : ';'
 - * ;
 - */
 - EmptyStatement() {
 - this._eat(';');
 - return {
 - type: 'EmptyStatement'
 - }
 - }