章节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
相关推荐
涡能增压发动积12 小时前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
Wenweno0o12 小时前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
swg32132112 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
tyung12 小时前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
gelald13 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
殷紫川13 小时前
深入拆解 Java 内存模型:从原子性、可见性到有序性,彻底搞懂 happen-before 规则
java·后端
元宝骑士13 小时前
FIND_IN_SET使用指南:场景、优缺点与MySQL优化策略
后端·mysql
用户319523703477113 小时前
记一次 PostgreSQL WAL 日志撑爆磁盘的排查
后端
nghxni13 小时前
LightESB PlatformHttp v3.0.0:JSONPath 订单转换 HTTP 路由实战
后端
武子康14 小时前
大数据-263 实时数仓-Canal 增量订阅与消费原理:MySQL Binlog 数据同步实践
大数据·hadoop·后端