Java PetitParser提取标记内容报预期输入结束错误如何解决
问题根因分析
- 当前报错是因为所有
expr匹配完成后,输入末尾的剩余else内容没有被任何规则消耗,导致start规则末尾的end()校验失败,触发「期望输入结束」的异常 - 原有
else规则强制要求必须匹配换行符nl,如果输入最后一行没有换行,哪怕想匹配末尾内容也会失败 - 你把跨expr的else匹配放到了expr规则内部,最后一个expr结束后没有下一个
expr_start作为starLazy的终止条件,导致末尾else无法匹配
可行修改方案
按照以下逻辑调整规则即可解决问题:
- 把前后else的匹配逻辑从expr规则中剥离,放到外层start规则统一处理,避免内部逻辑冲突
- 重构else规则,支持两种场景:带换行的普通行、无换行的末尾行
- 调整start规则结构为「开头可选else + (expr + 中间可选else).star() + 末尾可选else + end()」,确保所有输入内容都被完全消耗
- 简化expr规则,只负责匹配start标记到end标记的区间,专心返回Body对象
修改后的完整代码
public class ExpressionDefinition extends GrammarDefinition { { // 整体规则:消耗所有内容后结束 def("start", ref("else").optional() .seq(ref("expr").star( // 每个expr后面跟可选的中间else ref("else").optional() )) .seq(ref("else").optional()) .end() // 遍历结果只保留Body对象 .map(in -> { List<Object> all = (List<Object>) in; return all.stream() .filter(o -> o instanceof Body) .toList(); }) ); def("nl", of("\r\n").or(of("\n")).or(of("\r"))); // 简化expr规则:只匹配起止标记和中间内容 def("expr", ref("expr_start") .seq(ref("expr_body")) .seq(ref("expr_end")) .map(in -> ((List<Object>)in).get(1)) // 直接返回body ); def("expr_start", of("--Start Query").seq(any().starLazy(ref("nl")), ref("nl"))); def("expr_body", any().starLazy(ref("expr_end")).flatten().map((String in) -> new Body(in))); // 修正end规则:兼容后面无换行的场景 def("expr_end", of("--End").seq(any().starLazy(ref("nl").or(end())))); // 重构else规则:支持末尾无换行场景 def("else", // 要么是带换行的普通行,要么是到输入结束的内容 any().starLazy(ref("nl").or(ref("expr_start")).or(end())) .seq(ref("nl").optional()) ); } }
验证效果
修改后运行解析代码,返回结果就是所有提取到的Body对象列表,对应示例输入中的important 1和important 2,同时兼容输入末尾有无换行的场景,不会出现贪婪吞掉后续expr、无限循环的问题,也不会有残留内容导致结束校验失败。
内容的提问来源于stack exchange,提问作者msangel
相关产品推荐
相关产品推荐

