设计
ANTLR生成的分析器是自顶向下的,所以在对语法进行设计时也推荐使用自顶向下的方式,这种分析方式比自底向上的方式更贴近人类的思维和表达方式。
比如我们的TestL规则,一份TestL文件(doc)包含很多的条目(item),每个item由几个部分组成(tokens)……。
然后写出规则就可以了。
基本规则的表示
- [],任意一个[]内的字符,比如[0-9]表示任意一个数字
- ~[],任意一个不是[]内的字符,比如 ~[0-9]表示不能为数字。
- +,表示之前的字符,至少有一个,比如[a-z]+表示任意英文字母串。
- *,表示之前的字符,可以0个,可以多个。比如[a-z]*表示可以0个字母,也可以表示一个字母串。
- |,表示或
- ?,表示出现0次或1次
- [1-9][0-9],表示一个数,如123。其首位不能为0。
- \t,tab键。
- \r,回车键。
- \n,换行符。
windows下一个换行是用\r\n表示的。空白符往往使用[ \r\n\t]表示,第一个是空格。
‘blabla’,单引号包围起来的可以理解成关键字,antlr会将之作为一个整体,识别成一个token。
TestL规则的诠释
grammar TestL;
doc: item+;
item: NAME '{' STRING '}' AGE '{' NUM '}' HEIGHT '{' NUM '}';
NAME: 'Name';
AGE: 'Age';
HEIGHT: 'Height';
STRING: ~[ \t\r\n{}0-9]+;
NUM: [1-9][0-9]*;
SPACE: [ \t\r\n]->skip;
grammar TestL;
说明该语法名为TestL。如果把lexer和parser分开,则使用lexerGrammar和parserGrammar。
doc:item+;
说明doc结构由至少一条item结构组成。
item: NAME ‘{’ STRING ‘}’ AGE ‘{’ NUM ‘}’ HEIGHT ‘{’ NUM ‘}’;
说明item结构由后面这些结构组成。
NAME: ‘Name’;
AGE: ‘Age’;
HEIGHT: ‘Height’;
这三个是关键词,类似于cpp里的关键词一样,作为一个整体token。
STRING: ~[ \t\r\n{}0-9]+;
多个非数字非空白非大括号的字符构成的串。
NUM: [1-9][0-9]*;
首位不为0的数,比如123。但0123是不合法的。
SPACE: [ \t\r\n]->skip;
空白符号跳过,不做任何处理。
优先级的处理
有时候两种结构在某些条件下无法区分,这就牵扯到解析优先级的问题。
比如上面的STRING和NUM。假如两者定义为
STRING: ~[ \t\r\n{}]+;
NUM: [1-9][0-9]*;
则当面对一个12345的串时,是没有办法区别开的,它适用于STRING规则,也适用于NUM规则。antlr会自动处理这种情况,优先使用前面的规则解析之。当STRING在前面时,12345就会解析成STRING,反之解析成NUM。
我们测试一下。
修改TestL.g4文件,重新生成java文件,编译,并使用grun加载。输入:
Name{小强}Age{12}Height{150}
ctrl+z
然后解析出错了:
12被解析成了STRING,但这里需要的是NUM。当我们把两个规则的顺序颠倒一下,如下:
NUM: [1-9][0-9]*;
STRING: ~[ \t\r\n{}]+;
重新生成、编译、加载之,输入:
Name{小强}Age{12}Height{150}
ctrl+z
成功解析,如图: