如何用ANTLR4解析可作为格式化文本内容的可选分隔符?
自定义配置格式的ANTLR4解析优化需求
输入示例
// Example 1: /* $NAME = John "Blue" Doe $AGE = 20 $DESCRIPTION = Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et ==================================== Bytes: - Byte 0 = example 1 - Byte 1 = example 2 - Byte 3 = example 3 $ENDDESCRIPTION $CITY = London $DISTRICT = Sutton */ // Example 2: /* $NAME = Jane "Ruby" Doe $AGE = 28 $DESCRIPTION Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. ==================================== At vero eos et accusam et justo duo dolores et ea rebum. $ENDDESCRIPTION $CITY = Berlin $DISTRICT = Spandau */
需求
- 存在大量键值对:
keywords定义为前缀$后跟大写字母。pairs通常以=分隔,仅$DESCRIPTION的=分隔符可选(遗留文件已有30多年历史),$ENDDESCRIPTION无分隔符,用于标记文本结束。values通常为“分隔符到换行符之间的内容”,仅$DESCRIPTION的值为可跨多行的格式化文本,以$ENDDESCRIPTION结束。
- 部分行可包含多个关联赋值(如
$CITY = London $DISTRICT = Sutton) $DESCRIPTION的格式化文本内容可包含=(如作为分隔线)
我的尝试
解析器语法(Parser.g4)
parser grammar Parser; options { tokenVocab = Lexer; } test : data* EOF ; data : keyValuePair+ ; // $KEYWORD = VALUE keyValuePair : keyword ASSIGN value | (DESCRIPTION ) ASSIGN? text+ DESC_END ; keyword : KEYWORD ; value : VALUE ; text : TEXT ;
词法分析器语法(Lexer.g4)
lexer grammar Lexer; COMMENT : '//' ~[\r\n]* -> channel(HIDDEN) ; DESCRIPTION : '$DESCRIPTION' -> mode(DESC) ; KEYWORD : '$'[A-Z]+ ; ASSIGN : '=' -> mode(REGULAR) ; WS : [ \t\r\n]+ -> skip ; ANY : . -> skip ; mode DESC; DESC_END : '$ENDDESCRIPTION' -> mode(DEFAULT_MODE) ; DESC_ASSIGN : '=' -> type(ASSIGN) ; TEXT : (WORD ((' ' | '\t')+ WORD)* | ASSIGN+ ) ; DESC_WS : WS -> skip ; DESC_ANY : ANY -> skip ; mode REGULAR; REGULAR_END : [\r\n]+ -> mode(DEFAULT_MODE), channel(HIDDEN) ; REGULAR_KEYWORD : KEYWORD -> type(KEYWORD), mode(DEFAULT_MODE) ; // take care of multiple assignments per line VALUE : WORD ((' ' | '\t')+ WORD)* ; REGULAR_WS : WS -> skip ; REGULAR_ANY : ANY -> skip ; fragment WORD : CHAR+ ; fragment CHAR : [a-zA-Z0-9_?!@&%()<>|,.:;'\"*+/#=-] ; //must NOT contain $ symbol
问题
解析$DESCRIPTION的可选分隔符时出现异常:进入DESC模式后,Example 2能正常处理,但Example 1中$DESCRIPTION后的=会被包含在TEXT token中(值为"= Lorem ipsum...")。虽然后处理可以去除,但希望直接在语法层面解决;同时觉得CHAR的正则表达式写法不够合理,另外想知道有没有更好的方法收集完整的格式化文本用于后续输出。
改进建议
1. 修复DESCRIPTION后的=识别问题
ANTLR词法分析器会优先匹配更早定义的规则,因此在DESC模式中,将DESC_ASSIGN放在TEXT之前,同时调整TEXT规则不再包含=:
mode DESC; DESC_END : '$ENDDESCRIPTION' -> mode(DEFAULT_MODE) ; DESC_ASSIGN : '=' -> type(ASSIGN) ; // 匹配除$之外的所有内容(包括换行),直到遇到$ENDDESCRIPTION TEXT : (~'$' | '$' ~[E] | '$E' ~[N] | '$EN' ~[D] | '$END' ~[D] | '$ENDD' ~[E] | '$ENDDES' ~[C] | '$ENDDESC' ~[R] | '$ENDDESCR' ~[I] | '$ENDDESCRI' ~[P] | '$ENDDESCRIP' ~[T] | '$ENDDESCRIPT' ~[I] | '$ENDDESCRIPTIO' ~[N])+ ; DESC_WS : [ \t]+ -> skip ; // 只跳过空格和制表符,保留换行以维持文本格式
这样$DESCRIPTION后的=会被优先识别为ASSIGN,不会混入TEXT内容。
2. 优化CHAR正则表达式
原枚举式的CHAR规则容易遗漏字符,改为排除不允许的字符(仅排除$):
fragment CHAR : ~[$] ; // 除$之外的所有字符,包括换行、特殊符号等
3. 收集完整格式化文本的方法
方案一:解析器层面拼接
修改解析器规则,将描述文本的片段统一捕获,后续在监听器/访问器中拼接:
keyValuePair : keyword ASSIGN value | DESCRIPTION ASSIGN? descText DESC_END ; descText : TEXT+ ; // 捕获所有TEXT片段,后续可直接拼接成完整文本
方案二:词法层面直接生成完整token
在DESC模式中定义一个匹配完整描述内容的token,无需后续拼接:
mode DESC; DESC_END : '$ENDDESCRIPTION' -> mode(DEFAULT_MODE) ; SKIP_ASSIGN_WS : ('='? [ \t]+) -> skip ; // 跳过可选的=和后续空白 // 匹配从当前位置到$ENDDESCRIPTION的所有内容 DESCRIPTION_FULL_TEXT : (~'$' | '$' ~[E] | '$E' ~[N] | '$EN' ~[D] | '$END' ~[D] | '$ENDD' ~[E] | '$ENDDES' ~[C] | '$ENDDESC' ~[R] | '$ENDDESCR' ~[I] | '$ENDDESCRI' ~[P] | '$ENDDESCRIP' ~[T] | '$ENDDESCRIPT' ~[I] | '$ENDDESCRIPTIO' ~[N])+ ;
此方案可直接得到完整的描述文本,简化后续处理。
4. 其他优化点
- 移除全局的
ANY : . -> skip ;规则,避免ANTLR跳过未匹配的字符,方便调试未覆盖的语法场景。 - 保留
REGULAR模式中的REGULAR_KEYWORD规则,确保单行多赋值的场景能正确切换模式。
内容的提问来源于stack exchange,提问作者Haikun
相关产品推荐
相关产品推荐

