章节11:使用括号()标明优先级

使用括号()标明优先级,最重要的还是语法图。

先回顾上一个语法图

js 复制代码
programAst : termAst ((PLUS|MINUS) termAst)* 
termAst : factorAst ((MUL|DIV) factorAst)* 
factorAst: INTEGER

我们明确了一个规律,语法图越往下优先级越高,带括号优先级最高,所以在termAst和programAst之下。 和数字相比,数字本身就明确展示无运算,所以是同一个优先级。

修改语法图

js 复制代码
programAst : termAst ((PLUS|MINUS) termAst)* 
termAst : factorAst ((MUL|DIV) factorAst)* 
factorAst: INTEGER | LBRACKET programAst RBRACKET

修改词法解析器

java 复制代码
// 词性枚举
public enum TokenType {
    INTEGER // 数字
    , PLUS // 加法运算符
    , EOF // 程序结束
    , MINUS // 减法运算符
    , MUL // 乘法运算符
    , DIV // 除法运算符
    , LBRACKET // 左括号
    , RBRACKET // 右括号
}
// 词法解析器
public class Lexer {
    private String text; // 输入的程序
    private Integer position; // 记录扫描的位置
    private Character currentChar; // 记录当前扫描的字符
   
    public Token getNextToken(){  // 获取词法单元
        while(this.currentChar != null){
            if(Character.isDigit(this.currentChar)){
                return this.integer();
            }else if(Character.isWhitespace(currentChar)){
                this.skipWhiteSpace();
            }else if(this.currentChar == '+'){
                Token token = new Token(TokenType.PLUS , "+");
                this.advance();
                return token; 
            }else if(this.currentChar == '-'){
                Token token = new Token(TokenType.MINUS , "-");
                this.advance();
                return token; 
            }else if(this.currentChar == '*'){
                Token token = new Token(TokenType.MUL , "*");
                this.advance();
                return token; 
            }else if(this.currentChar == '/'){
                Token token = new Token(TokenType.DIV , "/");
                this.advance();
                return token; 
            }else if(this.currentChar == '/'){
                Token token = new Token(TokenType.DIV , "/");
                this.advance();
                return token; 
            }else if(this.currentChar == '('){
                Token token = new Token(TokenType.LBRACKET , "(");
                this.advance();
                return token; 
            }else if(this.currentChar == ')'){
                Token token = new Token(TokenType.RBRACKET , ")");
                this.advance();
                return token; 
            }else {
                this.error("未知的词法");
            }
        }
        return new Token(TokenType.EOF);
    }

    public Token integer(){ // 识别多个数字
        String result = "";
        while(this.currentChar != null && Character.isDigit(this.currentChar)){
            result += this.currentChar;
            this.advance();
        }
        return new Token(TokenType.INTEGER ,Integer.valueOf(result));
    }

    private void skipWhiteSpace(){ // 空格跳过
        while(currentChar != null && Character.isWhitespace(currentChar)){
            this.advance();
        }
    }

    public void advance(){ // 往后走一步
        this.position += 1;
        if(this.position <= this.text.length() - 1){ // 扫描的位置有效
            this.currentChar = text.charAt(this.position);
        }else{ // 扫描完了
            this.currentChar = null;
        }
        
    }
    public void error(String msg){ // 报错函数
        throw new RuntimeException(msg);
    }
    public Lexer(String text) {// 构造器
        this.text = text;
        this.position = 0;
        this.currentChar = text.charAt(this.position);
    }
}

修改语法解析器

java 复制代码
// 语法解析器
public class Parser {
    private Lexer lexer ; // 词法解析器
    private Token currentToken; // 当前的词法单元
    public Parser(Lexer lexer) {
        this.lexer = lexer;
        this.currentToken = this.lexer.getNextToken();
    }
    public Ast programAst(){ // 程序节点
        // programAst : termAst ((PLUS|MINUS) termAst)*
        Ast node = this.termAst();
        while(Arrays.asList(TokenType.PLUS,TokenType.MINUS).contains(this.currentToken.getType())){
            Token op = this.currentToken;
            if(op.getType() == TokenType.PLUS){
                this.eat(TokenType.PLUS);
            }else if(op.getType() == TokenType.MINUS){
                this.eat(TokenType.MINUS);
            }
            node = new ProgramAst(node ,op.getType(),this.termAst());
        }
        return node;
    }
    public Ast termAst(){
        // termAst : factorAst ((MUL|DIV) factorAst)*
        Ast node = this.factorAst();
        while(Arrays.asList(TokenType.MUL,TokenType.DIV).contains(this.currentToken.getType())){
            Token op = this.currentToken;
            if(op.getType() == TokenType.MUL){
                this.eat(TokenType.MUL);
            }else if(op.getType() == TokenType.DIV){
                this.eat(TokenType.DIV);
            }
            node = new TermAst(node ,op.getType(),this.factorAst());
        }
        return node;
    }
    public Ast factorAst(){
         // INTEGER | LBRACKET programAst RBRACKET
        Token left = this.currentToken;
        if(left.getType() == TokenType.INTEGER){
            this.eat(TokenType.INTEGER);
            return new FactorAst((Integer)left.getValue());
        }else if(left.getType() == TokenType.LBRACKET){
            this.eat(TokenType.LBRACKET);
            Ast ast = this.programAst();
            this.eat(TokenType.RBRACKET);
            return ast;
        }
        this.error("语法错误");
        return null;
    }
    public void eat(TokenType tokenType){ // 确认当前的词性是否正确
        if(tokenType == this.currentToken.getType()){
            this.currentToken = this.lexer.getNextToken();
        }else{
            this.error("语法错误");
        }
    }
    public void error(String msg){ // 报错函数
        throw new RuntimeException(msg);
    }
    public Ast parse(){ // 获取语法树
        return this.programAst();
    }
}

解释器不修改

执行测试

java 复制代码
private static void testInterpreter() {
    Lexer lexer = new Lexer(" (2 + 1) * 4");
    Parser parser = new Parser(lexer);
    Interpreter interpreter = new Interpreter(parser);
    Integer result = interpreter.expr();
    System.out.println("计算结果:" + result);
}

打印结果  计算结果:12
相关推荐
XUN4J11 分钟前
深入解析MySQL事务与锁:构建高并发数据系统的基石
后端·面试
徐行code16 分钟前
C++核心机制-复制消除
后端
开心猴爷32 分钟前
在 CICD 中实践 Fastlane + Appuploader 命令行,构建可复制的 iOS 自动化发布流程
后端
一 乐44 分钟前
高校评教|基于SpringBoot+vue高校学生评教系统 (源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
疯狂的程序猴1 小时前
Web 抓包完整实践指南,从浏览器网络调试到底层数据流捕获的全流程方案
后端
喵手1 小时前
我使用openEuler构建出了一个自愈式系统监控平台
后端
调试人生的显微镜1 小时前
以 uni-app 为核心的 iOS 上架流程实践, 从构建到最终提交的完整路径
后端
喵手1 小时前
我在openEuler上从零开始构建云原生AI应用
后端
解读玫瑰2 小时前
WSL+openEuler嵌入式开发实战:交叉编译与QEMU仿真全流程
后端
Stream2 小时前
加密与签名技术之数字签名算法
后端