废话不说了,我们直接开始:
1、请在Eclipse中建立一个新的项目,名叫Simple2,在classpath中,要加入antlr.jar
2、再下载一个文件:simple2.g,放在这个项目的路径下
3、在DOS窗口下,输入antlr simple2.g
4、回到eclispe,按F5刷新,会看到多出不少文件来。
5、再新建一个类Main,输入以下代码:
import java.io.*; |
6、新建一个文本文件test.txt,输入内容:
06/06/82 Peter 20;
03/04/83 Rosie 19;
04/05/81 Mikey 21;
7、运行Mainm,就能看到输出结果:
Name: Peter, Age: 20, DOB: 06/06/82
Name: Rosie, Age: 19, DOB: 03/04/83
Name: Mikey, Age: 21, DOB: 04/05/81
下面来解释一下,这里面的原理。先来看simple2.g的内容:
class SimpleParser extends Parser; entry : (d:DOB n:NAME a:AGE(SEMI) class SimpleLexer extends Lexer; NAME : ('a'..'z'|'A'..'Z')+; DOB : ('0'..'9' '0'..'9' '/')=> WS : SEMI : ';' ; |
要理解这个文件,我们得从下往上读,才能渐入佳境
最简单的是:
SEMI : ';';
SEMI是一种符号,对于词法分析器来说,只要它读到一个字符是“;”,它就认为自己是读到了一个SEMI
第二简单的是:
NAME : ('a'..'z'|'A'..'Z')+;
当词法分析器读取一个一个的字符的时候,当他读到一个以上的字母(无论大小写),它都把它们连在一起,作为一个NAME。()+,就是出现一次以上的意思。()*,就是出现0次以上的意思。
第三简单的是:
WS :
(' '
| '\t'
| '\r' '\n' { newline(); }
| '\n' { newline(); }
)
{ $setType(Token.SKIP); }
;
任何一个词法分析中定义的词,都有这样的格式:
[名称] : [定义] ;
定义的格式,又可以是一个或多个定义,';'就是一个定义,' '|'\t'就是两个定义。
每个定义之后,都可以跟一段代码,在
其中处理程序部分是可以省略的。
对于WS的定义,就是(' '|'\t'|'\r''\n'|'\n') 。但是,我们在这个定义之中,发现了三个嵌有代码的地方:'\r''\n'和'\n'后面,都有一句话,newline(),这是告诉词法分析器,行号计数器加一。这样在出现词法、语法错误时,就能报告一个准确的行号了。
在整个定义完成之后,还有一行代码$setType(Token.SKIP);,这是代码调用一个antlr的内置函数,告诉词法分析器,以上遇到的这四种字符情况,都请一律跳过。
最难的一种:
DOB : ('0'..'9' '0'..'9' '/')=>
(('0'..'9')('0'..'9')'/')(('0'..'9')('0'..'9')'/')('0'..'9')('0'..'9') //{ $setType(DOB); }
| ('0'..'9')+ { $setType(AGE); } ;
为什么说这个是最难的一种,因为这篇blog我本来按照每天一篇的进度,是该在昨天发出来的,结果我想了一天,才终于想通这个语法的意义。
DOB,是一种单词,但是这个单词不是一次分析出来的,而是有一个试错的过程。
DOB的格式是00/00/00,而AGE的格式是00。这样要区分两个单词,就相当困难。
而现在的这个定义,则分为三个部分 (...)=>(...)|(...)。这相当于一般语言中的三元表达式:
(1)?(2):(3)。如果式1为真,则返回式2的值,否则返回式3的值。
DOB与AGE的区别在于第三个字符,如果('0'..'9' '0'..'9' '/')的尝试判断无误,则进一步分析这剩下的字符,是否符合DOB的格式。那//注释后面的代码,之所以被注释起来,就是因为他其实不用执行,也是DOB类型了。而如果('0'..'9' '0'..'9' '/')的尝试判断出错,则只能按照 ('0'..'9')+的判断来分析试一试,如果成立,就手工赋予一个类型AGE。
把这Lexer讲完以后,Parser其实是极为简单的,大家自己看吧。下一次我们讲讲表达式的ANTLR描述。
(未完待续)