
在 antlr4 中直接处理跨行关键字(如 ibm bms 中列 72 续行符导致的关键词拆分)极易使词法规则复杂化;推荐采用轻量级行级预处理器先行合并续行、过滤列后注释,再将规整后的文本交由 antlr4 解析,从而保持语法清晰、可维护性强。
在 antlr4 中直接处理跨行关键字(如 ibm bms 中列 72 续行符导致的关键词拆分)极易使词法规则复杂化;推荐采用轻量级行级预处理器先行合并续行、过滤列后注释,再将规整后的文本交由 antlr4 解析,从而保持语法清晰、可维护性强。
IBM BMS 语言具有严格的列格式约束:当关键字或参数过长时,需在第 72 列放置续行字符(如空格或特定符号),后续内容延续至下一行;同时,第 73 列及之后的所有字符均视为注释并应被忽略。例如:
DFHMDF POS=(3,39),LENGTH=9,INITIAL='VERSION :',ATTRB=(ASKIP,NO*00000250
RM) 00000260期望词法分析器将 ASKIP,NO 和 RM 合并识别为单个 ATTRB 参数值 "ASKIP,NORM",即 NORM 必须作为完整标识符 token 输出。
ANTLR4 的 lexer 设计面向流式字符输入,不原生支持“基于行上下文动态拼接 token”的语义(如跨行合并关键字)。若强行在 lexer 中通过 pushMode/popMode、自定义 Interpreter 或状态机追踪行号与列位置,不仅规则臃肿难调试,还会破坏 lexer 的确定性与性能,违背 ANTLR4 “分离关注点”的设计哲学。
✅ 最佳实践:引入轻量预处理器(Preprocessor)
在将源码送入 ANTLR4 CharStream 前,先执行一次纯文本行处理:
- 对每行截取前 72 列(索引 0–71);
- 若该行第 72 列(即
line.charAt(71))为续行符(如空格、-或+),则移除该字符,并将下一行非空白前缀拼接到当前行末尾; - 忽略第 73 列起所有内容(含空格);
- 保留原始换行符用于后续错误定位(可选:用
\n替换为单个空格以消除换行影响,或保留换行为lexer提供行号信息)。
以下是一个 Java 示例预处理器片段:
public static String preprocessBmsSource(String input) {
String[] lines = input.split("\r\n|\r|\n");
StringBuilder normalized = new StringBuilder();
for (int i = 0; i < lines.length; i++) {
String line = lines[i].length() > 72 ? lines[i].substring(0, 72) : lines[i];
// 检查第72列是否为续行符(示例:空格)
boolean continues = line.length() == 72 && line.charAt(71) == ' ';
if (continues) {
line = line.substring(0, 71); // 移除续行符
}
normalized.append(line);
if (continues && i + 1 < lines.length) {
// 取下一行前导非空白部分(跳过缩进,但BMS要求严格列对齐,通常直接拼接)
String nextLine = lines[i + 1];
// BMS中续行内容通常从列16或25开始,此处简化为取有效内容(去首尾空格)
String content = nextLine.trim();
if (!content.isEmpty()) {
normalized.append(content);
}
i++; // 跳过已合并的下一行
}
normalized.append('\n');
}
return normalized.toString();
}处理后,上述样例将变为:
DFHMDF POS=(3,39),LENGTH=9,INITIAL='VERSION :',ATTRB=(ASKIP,NORM)
此时,ANTLR4 lexer 可使用简洁、正交的规则匹配关键字:
ATTRB : 'ATTRB' '=' '(' [A-Z]+ (',' [A-Z]+)* ')' ;
ID : [A-Z][A-Z0-9]* ;
WS : [ \t\r\n]+ -> skip ;⚠️ 注意事项:
- 预处理器必须严格遵循 BMS 规范(如续行符位置、列计数是否含 CR/LF、是否允许嵌套续行等),建议参考 IBM 官方手册确认细节;
- 错误定位需同步更新:预处理后的字符偏移 ≠ 原始文件偏移,可在预处理器中维护
OriginalPositionMap映射,或改用ANTLRInputStream的setLine()/setColumn()接口注入原始行列信息; - 不要试图在 lexer 中调用 Java 代码(如
-> {skip();})来读取下一行——ANTLR4 lexer 是单向流,无法回溯或前瞻多行。
总结:面对列敏感、续行驱动的语言,将格式规整逻辑前置到预处理层,是比在 ANTLR4 lexer 内“硬编码行逻辑”更健壮、可测试、可复用的工程选择。这并非妥协,而是合理分层——让 lexer 专注词汇识别,让预处理器承担格式归一化职责。

















