块语句与嵌套作用域
在上一篇文章中,我们已经添加了对语句的解析。目前仅支持表达式语句。在本篇文章中,我们添加对 块语句 的解析。
块语句
块语句,在 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'
- }
- }