公司里面有自定义的一个协议,服务器是C++写的而我的工作则是通过Java和这个服务器通讯,协议主体的解析是参考C++的源文件中定义的结构体来解析,如下所示:
typedef struct ExSecFreeTxtGroup { unsigned char cHeader; unsigned char cGroupLen; unsigned long sStockCd; char cFreeTxt[2][20]; } ExSecFreeTxtGroup;
由于这样的结构体比较多,虽然公司以前也有类似的解析类可以重用,但是有也有弊端,对于现在的准备的新项目扩展不便。所以决定重写一遍这些类,但是由于太多,而且主体部分的解析是重复工作,也就意味着可以自动生成。所以有了借助于javaCC来解析生成Java文件的想法,于是乎看了两天。虽然最终没弄成功,但至少了解了javaCC的一些用法。
下面是我编写的.jj文件,简单实现了对结构体的语法分析和提出我想要的关键字并且排除了注释和"#"开始的宏定义.
options { LOOKAHEAD = 1; CHOICE_AMBIGUITY_CHECK = 2; OTHER_AMBIGUITY_CHECK = 1; STATIC = false; DEBUG_PARSER = true; DEBUG_LOOKAHEAD = true; DEBUG_TOKEN_MANAGER = false; ERROR_REPORTING = true; JAVA_UNICODE_ESCAPE = false; UNICODE_INPUT = false; IGNORE_CASE = false; USER_TOKEN_MANAGER = false; USER_CHAR_STREAM = false; BUILD_PARSER = true; BUILD_TOKEN_MANAGER = true; SANITY_CHECK = true; FORCE_LA_CHECK = false; } PARSER_BEGIN( Std3Parser ) package mislay.util; import java.util.*; import mislay.util.std3parser.*; import mislay.util.std3parser.Std3StructMetaInfo.RowType; public class Std3Parser { private Map<String,Std3StructMetaInfo> structs = new HashMap<String,Std3StructMetaInfo>(); private Std3StructMetaInfo getStruct(String typeName) { Std3StructMetaInfo metaInfo = structs.get(typeName); if(metaInfo == null) { structs.put(typeName,metaInfo = new Std3StructMetaInfo()); metaInfo.setTypeName(typeName); } return metaInfo; } public Map getStructs() { return structs; } } PARSER_END( Std3Parser ) void start() : {} { struct() } void struct() : { } { ("typedef""struct"structType())*<EOF> } void structType() : { Token typeName = null; Token typeAlias = null; } { typeName = <IDENTIFIER> <LBRACE>(row(typeName.image))+<RBRACE> typeAlias = <IDENTIFIER> <SEMICOLON> { Std3StructMetaInfo metaInfo = getStruct(typeName.image); metaInfo.setTypeAlias(typeAlias.image); } } void row(String typeName): { Token unsigneds = null; Token type = null; Token name = null; } { [unsigneds = "unsigned"] (type = "char" | type = "int" |type = "long" | type = "double" | type = <IDENTIFIER>) name = <IDENTIFIER> (<ROWNAME>)* <SEMICOLON> { Std3StructMetaInfo metaInfo = getStruct(typeName); RowType rowType = null; try { rowType = RowType.valueOf(type.image.toUpperCase()); } catch(Exception e) { rowType = RowType.REF; } metaInfo.addRow(unsigneds == null,rowType,name.image); } } SKIP : { " " | "\t" | "\n" | "\r" | <"//" (~["\n","\r"])* ("\n" | "\r" | "\r\n")> | <"/*" (~["*"])* "*" ("*" | ~["*","/"] (~["*"])* "*")* "/"> | <"#" (~["\n","\r"])* ("\n" | "\r" | "\r\n" )*> } TOKEN : { < IDENTIFIER: <LETTER> (<LETTER>|<DIGIT>)* > | < #LETTER: ["$","A"-"Z","_","a"-"z"] > | < #DIGIT: ["0"-"9"] > } TOKEN : { < LPAREN: "(" > | < RPAREN: ")" > | < LBRACE: "{" > | < RBRACE: "}" > | < LBRACKET: "[" > | < RBRACKET: "]" > | < SEMICOLON: ";" > | < COMMA: "," > | < DOT: "." > | < ROWNAME: "["(["0"-"9"])+"]" > }
如果认真看会发现.jj文件的结构很简单,除了BNF就是剩下的就是java的实现,但是最终没有完成还是因为BNF编写太复杂了.有些读取是有规则的而不是按照顺序读取到底,比如有某几个字段循环读取几次,或者按照某个字段的长度来循环读取,又或者说循环到整个结构的长度结束等等,还有些则是类型的问题。比如char[]一般读取是字符串,但是有的则不是。所以我为了这些条件需要在这个结构的基础上添加一些特殊的注解来表示某一规则比如@Loop(type=serial,length=32),当然还需要在.jj文件中添加BNF表达式了,而且条件比较多对于写这些BNF对于才接触两天的我是个挑战.嘿嘿,索性暂时放下吧,时间关系暂时留个标记在这,以后有时间在拾起吧。
后话:如果只考虑特定情况利用JavaCC还是很简单的,如果把所有情况都考虑进去编写.jj文件真的是个挑战。不过如果不考虑比较全面的情况就不会利用JavaCC了.利用正则表达式或许还方便快捷些.
如果对编译原理有了解会更加容易。还有就是这里指的BNF并不是标准的BNF。