自己写个JavaScript parser (分析器)系列 (3)
注:参考自http://dukeland.hk,本博客系列内容为自己解读的成果,以备将来自己回顾使用。所有版权归原作者所有,如有任何问题,请联系原作者。
这一部分我们到了parser,开始分析语义了。先上一段代码。
1,parser对象基本内容
function Parser(scanner){ this.scanner = scanner; this.currentToken = new Token(); this.lookaheadToken = new Token(); this.lookaheadToken.consumed = true; //看下面注释 } //读取一个token,这个名字歧义,别被误导了。调用这个函数的时候都是说当前的token已经处理完了,所以要再多读一个token来用。 Parser.prototype.nextToken = function (){ if (this.lookaheadToken.consumed){ var token = this.scanner.nextToken(); //skip comments while (token == Token.tokens.LINECOMMENT_TOKEN || token == Token.tokens.BLOCKCOMMENT_TOKEN){ token = this.scanner.nextToken(); } this.currentToken.type = token; this.currentToken.text = this.scanner.currentToken.text; return token; }else{ this.currentToken.type = this.lookaheadToken.type; this.currentToken.text = this.lookaheadToken.text; this.lookaheadToken.consumed = true; return this.currentToken.type; } } //lookahead就是在当前读词的基础上再多读一个词。还是为了获取上下文,好对有歧义的语法做出裁决。 //变量consumed其实相当于回退。我们多读了一个词用来做上下文,如果发现多读的这个词压根没用,按处理字符的办法,我们要做回退。 //但既然下一次循环又要多读一个词,何不把这个保留不回退,直接在下一次处理时用呢? //所以只需要一个标志位,如果给用掉了就true,没用掉下一次就不需要多读一个词,直接用它就可以了 Parser.prototype.lookahead = function (){ if (this.lookaheadToken.consumed){ var token = this.scanner.nextToken(); //skip comments while (token == Token.tokens.LINECOMMENT_TOKEN || token == Token.tokens.BLOCKCOMMENT_TOKEN){ token = this.scanner.nextToken(); } this.lookaheadToken.type = token; this.lookaheadToken.text = this.scanner.currentToken.text; this.lookaheadToken.consumed = false; return token; }else{ return this.lookaheadToken.type; } }
语义分析时的一个大问题就是错误处理,基本的要求是,不能因为某一行简单的语义错误就无法继续往下分析。一般的做法是,把这一行丢弃,从新的一行重新开始分析,同时打印错误。跳过语法错误的代码如下。
2,语法出错时丢弃整行,同时跳过错误。
//循环读取下一个token,直到新的一行 Parser.prototype.skipError = function (){ this.scanner.skipNewLine = false; while (this.lookahead() != Token.tokens.NEWLINE_TOKEN && this.lookahead() != Token.tokens.EOS_TOKEN){ this.nextToken(); } this.scanner.skipNewLine = true; }
3,以上准备好之后,下面就是对if语句块进行语法解析的代码了:
//这个函数只是“把握大局”,具体语句块的处理交给了下面两个函数 Parser.prototype.parseIfExpression = function (){ //consume "if" this.nextToken(); var condition = this.parseParenExpression(); var expressions = this.parseExpressionBlock(); var elseExpressions; if (this.lookahead() == Token.tokens.ELSE_TOKEN){ //consume "else" this.nextToken(); elseExpressions = this.parseExpressionBlock(); } return new IfNode(condition, expressions, elseExpressions); } //这里把if的两个大括号之间的内容当做一个节点来处理,其中细节又递归的调用parseExpression()来做处理,有点递归的意思 Parser.prototype.parseExpressionBlock = function (){ //如果紧跟if或其它符号的不是'{',这里当做语法错误处理 if (this.lookahead() != Token.tokens.LEFTBRACE_TOKEN){ Errors.push({ type: Errors.SYNTAX_ERROR, msg: "Expecting \"{\"", line: this.scanner.currLine }); }else{ this.nextToken(); } var block = new ExpressionBlockNode(); var expressions = this.parseExpressions(block); if (this.lookahead() != Token.tokens.RIGHTBRACE_TOKEN){ Errors.push({ type: Errors.SYNTAX_ERROR, msg: "Expecting \"}\"", line: this.scanner.currLine }); }else{ this.nextToken(); } return block; } //对if条件里内容的解析 Parser.prototype.parseParenExpression = function (){ if (this.lookahead() != Token.tokens.LEFTPAREN_TOKEN){ Errors.push({ type: Errors.SYNTAX_ERROR, msg: "Expecting \"(\"", line: this.scanner.currLine }); }else{ this.nextToken(); } var expression = this.parseExpression(); if (this.lookahead() != Token.tokens.RIGHTPAREN_TOKEN){ Errors.push({ type: Errors.SYNTAX_ERROR, msg: "Expecting \")\"", line: this.scanner.currLine }); }else{ this.nextToken(); } return expression; }
至于其它种类的情况,和这个类似,可以按照这个模式做补充。