自己写个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;
}

至于其它种类的情况,和这个类似,可以按照这个模式做补充。

posted @ 2012-09-11 22:47  yunfan85  阅读(527)  评论(0编辑  收藏  举报