ANTLR C#实现Bencode解析:如何按前令牌值限制字符串长度?
解决ANTLR中Bencode字符串长度依赖的问题
在ANTLR中无法直接修改只读的StopIndex属性来限制当前令牌长度,不过可以通过在Lexer中添加自定义动作手动控制字符读取长度的方式实现Bencode字符串的解析,具体方案如下:
核心思路
Bencode字符串的格式是<正整数>:<对应长度的字符序列>,我们可以在Lexer中先匹配前缀的数字和冒号,然后通过代码获取数字对应的长度,再手动从输入流中读取该长度的字符,最后将这些字符设置为当前令牌的文本,从而生成正确的字符串令牌。
完整语法示例
Lexer语法(BencodeLexer.g4)
lexer grammar BencodeLexer; // 匹配Bencode字符串前缀(数字+冒号),并手动读取指定长度的字符作为字符串内容 STRING: DIGITS ':' { // 提取前缀中的数字部分 String numStr = getText().substring(0, getText().length() - 1); int strLength; try { strLength = Integer.parseInt(numStr); if (strLength < 0) throw new NumberFormatException(); } catch (NumberFormatException e) { // 处理非正整数的非法格式 throw new LexerNoViableAltException(this); } // 读取指定长度的字符 char[] contentChars = new char[strLength]; for (int i = 0; i < strLength; i++) { if (_input.LA(1) == EOF) { // 输入流字符不足,抛出异常 throw new LexerNoViableAltException(this); } contentChars[i] = (char)_input.LA(1); _input.consume(); // 消费当前字符 } // 将令牌文本设置为读取到的字符串内容 setText(new String(contentChars)); }; // 匹配整数格式:i<数字>e INT: 'i' DIGITS 'e'; // 列表和字典的起始、结束标记 LIST_START: 'l'; DICT_START: 'd'; END: 'e'; // 辅助片段:匹配数字 fragment DIGITS: [0-9]+; // 忽略可能的空白(Bencode本身不允许空白,若有需要可保留) WS: [ \t\n\r]+ -> skip;
Parser语法(BencodeParser.g4)
parser grammar BencodeParser; options { tokenVocab=BencodeLexer; } // 顶层规则:Bencode值 bencode: value; // Bencode支持的四种类型 value: string | integer | list | dict; string: STRING; integer: INT; list: LIST_START value* END; dict: DICT_START (string value)* END;
关键细节说明
- 手动控制输入流消费:通过
_input.LA(1)查看当前字符,_input.consume()消费字符,完全替代了ANTLR自动的令牌边界计算,避开了StopIndex只读的限制。 - 错误处理:添加了数字格式校验(非正整数、格式错误)和输入流长度不足的异常抛出,保证语法的健壮性。
- 令牌文本重写:通过
setText()将原本的<数字>:替换为实际的字符串内容,让Parser可以直接使用STRING令牌处理字符串值。
替代方案(Parser层处理)
如果需要在Parser层处理长度验证,可以将前缀数字和冒号作为单独令牌,然后在Parser规则中获取数字值,再从输入流中读取对应长度的内容。但这种方法需要Parser直接操作输入流,代码复杂度更高,不如Lexer层处理简洁。
内容的提问来源于stack exchange,提问作者Deallocate
相关产品推荐
相关产品推荐

