You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用PetitParser解析非对称二元运算符时如何保证结合性正确?

嘿,这个问题我熟!用PetitParser处理减法这类左结合的非对称运算符时,核心就是要避免右递归的写法,改用专门的左结合组合器来处理。我给你一步步拆解解决办法:

问题根源

如果你的初始代码是用递归表达式定义(比如expr = term | expr '-' term),这种写法是右递归,会让解析器把10-5-3解析成10-(5-3),完全违背了减法左结合的运算规则。减法、除法这类非对称运算符必须是左结合的——也就是从左到右依次计算,先算10-5,再用结果减3。

解决方案:用chainLeft处理左结合

PetitParser提供了chainLeft组合器,专门用来处理左结合的二元运算符。它会把连续的运算项按左到右的顺序组合,完美匹配减法的运算逻辑。

完整示例代码

下面是修改后的Java代码,不仅能正确解析左结合的减法,还支持括号来强制改变运算顺序:

import org.petitparser.parser.Parser;
import org.petitparser.parser.combinators.SettableParser;
import static org.petitparser.parser.primitive.CharacterParser.digit;

public class SubtractionParser {
    public static void main(String[] args) {
        // 1. 解析非负整数
        Parser number = digit().plus().flatten().map(Integer::parseInt);
        
        // 2. 定义可设置的递归表达式解析器
        SettableParser expr = SettableParser.undefined();
        
        // 3. 基础项:数字 或者 括号包裹的表达式
        Parser term = number.or(expr.between(digit('('), digit(')')));
        
        // 4. 用chainLeft实现左结合的减法
        // 每次匹配到`term - term`,就把前一个结果和后一个数字做减法
        Parser subtractionExpr = term.chainLeft(
            digit('-').map(op -> (Integer a, Integer b) -> a - b)
        );
        
        // 5. 把表达式入口指向减法解析器
        expr.set(subtractionExpr);
        
        // 测试验证
        testParser(expr, "10-5-3");       // 输出:((10-5)-3) = 2
        testParser(expr, "10-(5-3)");     // 输出:10-(5-3) = 8
    }
    
    private static void testParser(Parser parser, String input) {
        try {
            Integer result = parser.parse(input).get();
            System.out.printf("%s 解析结果:%d(对应运算顺序:%s)%n", 
                input, result, getParenthesizedOrder(input));
        } catch (Exception e) {
            System.out.printf("%s 解析失败:%s%n", input, e.getMessage());
        }
    }
    
    // 辅助方法:生成带括号的运算顺序字符串(仅演示用)
    private static String getParenthesizedOrder(String input) {
        if (input.contains("(")) {
            return input;
        }
        String[] parts = input.split("-");
        if (parts.length <= 2) return input;
        StringBuilder sb = new StringBuilder("(" + parts[0] + "-" + parts[1] + ")");
        for (int i = 2; i < parts.length; i++) {
            sb.insert(0, "(").append("-" + parts[i] + ")");
        }
        return sb.toString();
    }
}
扩展到除法和多运算符优先级

除法和减法一样是左结合、非对称的,处理方式完全相同——只需要把运算符换成/,对应的lambda改成(a,b) -> a / b即可。

如果要同时支持加减乘除,还需要处理运算符优先级:乘除优先级高于加减。这时候要分层解析:先处理乘除(高优先级),再用乘除的结果作为加减的term:

// 乘除法:左结合,高优先级
Parser multiplicationDivision = term.chainLeft(
    digit('*').map(op -> (Integer a, Integer b) -> a * b)
        .or(digit('/').map(op -> (Integer a, Integer b) -> a / b))
);

// 加减法:左结合,低优先级
Parser additionSubtraction = multiplicationDivision.chainLeft(
    digit('+').map(op -> (Integer a, Integer b) -> a + b)
        .or(digit('-').map(op -> (Integer a, Integer b) -> a - b))
);

expr.set(additionSubtraction);

这样10-3*2就会被正确解析成10-(3*2),而不是(10-3)*2。

内容的提问来源于stack exchange,提问作者Idan Arye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:17:42