JavaCC词法与语法错误恢复语法编写及error_skipto()使用问题
我来帮你拆解这两个JavaCC错误恢复里的常见问题:
一、连续语法错误仅捕获第一条的原因与修复
你遇到的连续错误只触发一次捕获的情况,核心问题出在error_skipto的实现和错误恢复后的解析流程上。
当第一个错误被捕获并执行error_skipto(LINE_END, e)时,你的代码会一直跳过token直到找到LINE_END,但这里有个关键细节:跳过到LINE_END后,当前的token指针已经指向了下一个语句的起始(比如下一个syout),但你的imprimir()方法在执行完catch块后就直接结束了,没有回到语法规则的开头去尝试解析下一个语句。
另外,你的error_skipto实现还有个小问题:如果在跳过过程中遇到文件结束(EOF),会进入死循环,最好加上EOF的判断。
修改后的imprimir()规则和error_skipto可以这样写:
void imprimir(): {} { ( try{ <DATA_OUT><OPEN_PAREN>(<NUM_INT>| <NUM_FLOAT>|<CHAR>|<STRING>|<IDENTIFIER>) <CLOSE_PAREN><LINE_END> }catch(ParseException e){ error_skipto(LINE_END, e); } )* // 用循环处理多个syout语句,错误恢复后继续尝试解析下一个 } JAVACODE void error_skipto(int kind, ParseException ex){ System.out.println("语法错误:" + ex.toString()); Token t; do { t = getNextToken(); if (t.kind == EOF) { // 防止遇到文件结束死循环 System.out.println("提前遇到文件结束,终止错误恢复"); return; } } while (t.kind != kind); }
这样修改后,每次错误恢复到LINE_END后,循环会让解析器回到imprimir()的开头,尝试解析下一个syout语句,就能捕获所有连续的错误了。
二、TokenMgrError无法在语法规则内捕获的原因
JavaCC里的TokenMgrError是词法分析器抛出的错误,它的抛出时机比语法分析的try-catch更早:词法分析器在生成token的时候就会抛出这个错误,此时还没进入到你写的语法规则(imprimir())的try-catch块中。
要在语法层面处理词法错误,你需要修改词法分析器的错误处理逻辑,而不是在语法规则里加catch。具体做法是:
- 在JavaCC的语法文件开头,添加
TOKEN_MGR_DECLS块,重写词法分析器的错误处理方法:
TOKEN_MGR_DECLS: { @Override public void LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorMsg, String curLex) { // 自定义词法错误处理,比如打印错误信息,然后跳过错误的token System.out.println("词法错误:行" + errorLine + ",列" + errorColumn + ":" + errorMsg); // 这里可以实现跳过当前错误token的逻辑,比如继续读取直到合法token或换行 try { while (true) { int ch = input_stream.read(); if (ch == '\n' || ch == -1) { // 跳过到行尾或文件结束 break; } } } catch (IOException e) { e.printStackTrace(); } } }
- 关闭JavaCC默认的词法错误抛出:在语法文件开头加上
OPTIONS设置:
OPTIONS { ERROR_REPORTING = false; // 关闭默认错误报告,用自定义的LexicalError处理 }
这样,词法错误就会被你自定义的LexicalError方法处理,而不会抛到主函数的try-catch里了。
额外提示:错误恢复的最佳实践
- 尽量在语法规则的循环结构中处理错误恢复,确保错误后能回到正确的解析上下文
- 处理错误时一定要考虑EOF的情况,避免死循环
- 词法和语法错误的处理是分开的,词法错误要在词法分析器层面重写方法处理
内容的提问来源于stack exchange,提问作者eddie

