用ANTLR分析简单的十六进制格式字符串

本文介绍如何使用ANTLR的BNF语法解析十六进制格式的文本字符串,包括变长字符串和32位无符号整数的解析,并通过ANTLRWorks进行测试验证。

 

用ANTLR分析简单的十六进制格式字符串

 

一、问题:

假设现在有一个16进制格式的文本字符串,如下:

 

 

0x00, 0x06, 0x6E, 0x61, 0x6e, 0x61, 0x6d, 0x69,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
 

目标是解析其中的含义。

其中

0x00, 0x06, 0x6E, 0x61, 0x6e, 0x61, 0x6d, 0x69,

表示一个长度为6的字符串

0x6E, 0x61, 0x6e, 0x61, 0x6d, 0x69即"nanami"

而0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02表示两个32位无符号整数1和2。

 

二、思路:

如果用ANTLR的.g文件描述,大概是:

 

 

packet
: str16 uint32 uint32
;
 

意思是依次读取变长字符串、32位整数、32位整数。

我的想法是用ANTLR的谓词语法处理变长的字符串

完整的.g文件如下:

 

 

/*
Text :
0x00, 0x06, 0x6E, 0x61, 0x6e, 0x61, 0x6d, 0x69,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,

Start Rule : 
packets

Output	:
username : nanami
x : 1
y : 2
*/

grammar Binary;

@header {
}

@members {
	private int str_num = 0;
	private int str_length = 0;
}

packets
: (packet (',')? NEWLINE* )*
;

packet
: 
  username=str16 {System.out.println("username : " + $username.value);}
  x=uint32 {System.out.println("x : " + x);}
  y=uint32 {System.out.println("y : " + y);}
;

str16 returns [String value]
@init { str_num = 0; StringBuffer sb = new StringBuffer();}
@after { value = sb.toString();}
: s1=ushort16 {str_length = $s1.value;}
  (
	{str_num < str_length}?=>(
	   b1=byte8 {sb.append((char)$b1.value);}  
	   {str_num++;}
	)
  )*
;

byte8 returns [int value]
@init { value = 0; }
@after {}
: b1=BYTE8 
  {
	if ($b1.text.startsWith("0x")) {
	    value |= Integer.parseInt($b1.text.substring(2), 16);
	}
  }
  ',' NEWLINE?
;


ushort16 returns [int value]
@init { value = 0;}
@after {}
: b1=BYTE8 
  {
	if ($b1.text.startsWith("0x")) {
	    value |= Integer.parseInt($b1.text.substring(2), 16) << 8;
	}
  } ',' NEWLINE?
  b2=BYTE8 
  {
	if ($b2.text.startsWith("0x")) {
	    value |= Integer.parseInt($b2.text.substring(2), 16);
	}
  } ',' NEWLINE?
;

uint32 returns [int value]
@init { value = 0; }
@after {}
: b1=BYTE8 
  {
	if ($b1.text.startsWith("0x")) {
	    value |= Integer.parseInt($b1.text.substring(2), 16) << 24;
	}
  } ',' NEWLINE?
  b2=BYTE8 
  {
	if ($b2.text.startsWith("0x")) {
	    value |= Integer.parseInt($b2.text.substring(2), 16) << 16;
	}
  } ',' NEWLINE?
  b3=BYTE8 
  {
	if ($b3.text.startsWith("0x")) {
	    value |= Integer.parseInt($b3.text.substring(2), 16) << 8;
	}
  } ',' NEWLINE?
  b4=BYTE8 
  {
	if ($b4.text.startsWith("0x")) {
	    value |= Integer.parseInt($b4.text.substring(2), 16);
	}
  } ',' NEWLINE?
;

// LEXER

BYTE8: '0x' HexDigit HexDigit;
fragment
HexDigit: ('0'..'9'|'a'..'f'|'A'..'F');
WS: (' '|'\t'|'\u000C') {$channel=HIDDEN;};  
NEWLINE: ('\r')? '\n';
 

 

三、测试结果:

用ANTLRWorks测试,输入数据设置为

 

 

0x00, 0x06, 0x6E, 0x61, 0x6e, 0x61, 0x6d, 0x69,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
 

开始规则(Start Rule)设置为 packets

输出为:

 

 

username : nanami
x : 1
y : 2
 

 

四、总结:

相比起其它语言(诸如C的struct和Erlang的binary数据类型匹配),

用ANTLR的BNF语法处理二进制的解码会比较麻烦(需要先转换为文本型的字符串),

而且灵活性很小(虽然可以用谓词分析变长的字符串,但处理更复杂的结构会很困难)。

不过个人觉得这个问题对于了解ANTLR的语法和ANTLRWorks的测试环境很有帮助。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值