使用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
相关产品推荐
相关产品推荐

