章节10:支持连续的乘除法

支持连续的乘除法,最先需要思考的是语法图,在四则运算里面,乘除比加减优先级要高。

回顾之前的语法图

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

因为乘除的优先级要高,所以不可能和加减平级,语法图有个规律,越往下优先级越高,所以我们要在programAst和factorAst之间补充乘除的语法结构。

新的语法图

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

修改词法解析器

java 复制代码
// 词性枚举
public enum TokenType {
    INTEGER // 数字
    , PLUS // 加法运算符
    , EOF // 程序结束
    , MINUS // 减法运算符
    , MUL // 乘法运算符
    , DIV // 除法运算符
}

// 词法解析器
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 {
                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 复制代码
@Data
public class TermAst extends Ast{
    private Ast leftValue;
    private TokenType op;
    private Ast rightValue;
    public TermAst(Ast leftValue,TokenType op, Ast rightValue) {
        this.leftValue = leftValue;
        this.op = op;
        this.rightValue = rightValue;
    }
}
// 语法解析器
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(){
         // factorAst: INTEGER
        Token left = this.currentToken;
        this.eat(TokenType.INTEGER);
        return new FactorAst((Integer)left.getValue());
    }
    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 复制代码
// 目标执行器
public class Interpreter {
    private Parser parser; // 语法解析器
    public Interpreter(Parser parser) {
        this.parser = parser;
    }
    public Object visitProgramAst(Ast ast){ // 访问programAst节点
        ProgramAst programAst = (ProgramAst) ast; 
        if(programAst.getOp() == TokenType.PLUS){
            return (Integer)this.visit(programAst.getLeftValue()) + (Integer)this.visit(programAst.getRightValue()); // 加法计算
        }else if(programAst.getOp() == TokenType.MINUS){
            return (Integer)this.visit(programAst.getLeftValue()) - (Integer)this.visit(programAst.getRightValue()); // 减法计算
        }
        return null;
    }
    public Object visitTermAst(Ast ast){
        TermAst termAst = (TermAst) ast; 
        if(termAst.getOp() == TokenType.MUL){
            return (Integer)this.visit(termAst.getLeftValue()) * (Integer)this.visit(termAst.getRightValue()); // 乘法计算
        }else if(termAst.getOp() == TokenType.DIV){
            return (Integer)this.visit(termAst.getLeftValue()) / (Integer)this.visit(termAst.getRightValue()); // 除法计算
        }
        return null;
    }
    public Object visitFactorAst(Ast ast){
        FactorAst factorAst = (FactorAst) ast;
        return factorAst.getValue();
    }
    public Object visit(Ast ast){ // 使用反射通过类名调用对应的函数
        String methodName = "visit" + ast.getClass().getSimpleName();
        try {
            Method method = this.getClass().getDeclaredMethod(methodName , Ast.class );
            return method.invoke(this, ast);
        } catch (Exception e) {
            e.printStackTrace();
        } 
        return null;
    }
    public Integer expr() {
        Ast ast = parser.parse(); // 获取语法树
        Integer result = (Integer)this.visit(ast); // 遍历获取结果
        return result;
    }
}

执行测试

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

打印结果  计算结果:29
相关推荐
鬼火儿3 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin3 小时前
缓存三大问题及解决方案
redis·后端·缓存
间彧4 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧4 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧4 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧4 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧4 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧4 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧5 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang5 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构