在上一章(第三章)中我们用纯手工的方式构造了一个Pascal的扫描器(也称词法分析器)。细心的读者会想到,大部分语言的词法构造过程都差不多,都有变量ID,字符串,整数,浮点数,关键字,特殊符号等(如比较符,赋值,索引括号)等等。事实上在编译技术发展到今天,手写词法分析器基本很少了,因为编程语言的词不同于自然语言,很容易通过机械的手段实现。有很多工具可以生成词法分析器,比如Flex,JavaCC等。不过在本书中,不管词法还是语法还是代码生成,我将使用Antlr(http://www.antlr.org)这个大名鼎鼎的工具来完成后续的代码自动化工作。关于Antlr的基础我就不介绍了,网上有很多的教程。这儿我推荐两个:《使用 Antlr 开发领域语言 - 开发一个完整的应用》,《The Definitive ANTLR Reference》
==>> 本章中文版源代码下载:svn co http://wci.googlecode.com/svn/branches/ch3_antlr/ 源代码使用了UTF-8编码,下载到本地请修改!
Antlr的Lexer和Parser还有AST属于自动化产物,自成体系。编译器/解释器的非自动化部分都是通过"hook"手段灌进Antlr语法中。如果使用Antlr,将会抛弃原有框架但是复用原有的代码和逻辑,再者项目也从纯粹的Java项目变成了Maven项目。
1 创建一个Antlr文件Pascal.g,首先是抬头,我们使用了不同于PascalToken的另外一个Token类型PascalAntlrToken,因为对于Antlr来说,只能继承Antlr的CommonToken,而PascalToken继承自Token。
1: grammar Pascal;
2: options{
3: TokenLabelType=PascalAntlrToken;
4: output=AST;
5: }
6: tokens{
7: NUMBER_REAL;
8: }
这里为简单使用了混合语法,即Lexer和Parser的语法放在一起。因为Antlr认为词法的前向过程(LL)和语法的前向过程基本一样,这两个只是单位不一样,一个是character,一个是token。输出为AST,现在暂且不用管它。调用maven命令之后,此语法文件会生成两个Java文件:PascalLexer和PascalParser。
2 因为Pascal大小写不敏感,必须让Antlr支持忽略大小写:
1: fragment A:('a'|'A');
2: fragment B:('b'|'B');
3: fragment C:('c'|'C');
4: ...
5: ...
6: fragment X:('x'|'X');
7: fragment Y:('y'|'Y');
8: fragment Z:('z'|'Z');
3 建立Pascal关键字列表:
1: AND : A N D ;
2: ARRAY : A R R A Y ;
3: BEGIN : B E G I N ;
4: CASE : C A S E ;
5: CHAR : C H A R ;
6: CHR : C H R ;
7: CONST : C O N S T ;
8: DIV : D I V ;
9: DO : D O ;
10: DOWNTO : D O W N T O ;
11: ELSE : E L S E ;
12: END : E N D ;
13: FILE : F I L E ;
14: FOR : F O R ;
15: FUNCTION : F U N C T I O N;
16: GOTO : G O T O ;
17: IF : I F ;
18: IN : I N ;
19: INTEGER : I N T E G E R;
20: LABEL : L A B E L ;
21: MOD : M O D ;
22: NIL : N I L ;
23: NOT : N O T ;
24: OF : O F ;
25: OR : O R ;
26: PACKED : P A C K E D ;
27: PROCEDURE : P R O C E D U R E;
28: PROGRAM : P R O G R A M;
29: REAL : R E A L ;
30: RECORD : R E C O R D ;
31: REPEAT : R E P E A T ;
32: SET : S E T ;
33: THEN : T H E N ;
34: TO : T O ;
35: TYPE : T Y P E ;
36: UNTIL : U N T I L ;
37: VAR : V A R ;
38: WHILE : W H I L E ;
39: WITH : W I T H ;
1: PLUS : '+' ;
2: MINUS : '-' ;
3: STAR : '*' ;
4: SLASH : '/' ;
5: ASSIGN : ':=' ;
6: COMMA : ',' ;
7: SEMI : ';' ;
8: COLON : ':' ;
9: EQUAL : '=' ;
10: NOT_EQUAL : '<>' ;
11: LT : '<' ;
12: LE : '<=' ;
13: GE : '>=' ;
14: GT : '>' ;
15: LPAREN : '(' ;
16: RPAREN : ')' ;
17: LBRACK : '[' ;
18: LBRACK2 : '(.' ;
19: RBRACK : ']' ;
20: RBRACK2 : '.)' ;
21: POINTER : '^' ;
22: AT : '@' ;
23: DOT : '.' ;
24: DOTDOT
25: : '..' ;
26: LCURLY : '{' ;
27: RCURLY : '}' ;
5 上一章讲述了三种Token,单词Token,数字Token和字符串Token。对于单词Token来说,关键字已经在第三条列出,剩下的只是标识符即ID了。
标识符 Token:
ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
STRING
: '\'' ('\'\'' | ~('\''))* '\'' ;
1: NUMBER
2: : ('0'..'9')+
3: ( ( {(input.LA(2)!='.')&&(input.LA(2)!=')')}?
4: '.' {$type = NUMBER_REAL;}
5: ('0'..'9')+ (EXPONENT)?
6: )?
7: | EXPONENT {$type= NUMBER_REAL;}
8: )
9: ;
10: fragment
11: EXPONENT
12: : ('e') ('+'|'-')? ('0'..'9')+
13: ;
1: WS : ( ' '
2: | '\t'
3: | '\f'
4: | ( '\r\n'
5: | '\r'
6: | '\n'
7: )
8: {
9: }
10: )
11: { $channel=HIDDEN; }
12: ;
1: COMMENT
2: : '{'
3: (
4: : '\r' '\n'
5: | '\r'
6: | '\n'
7: | ~('}' | '\n' | '\r')
8: )*
9: '}'
10: {$channel=HIDDEN;}
11: ;
--hello.pas----------------
1行:PROGRAM[0] ID[8] LPAREN[14] ID[15] RPAREN[21] SEMI[22]
5行:VAR[0]
6行:ID[4] COLON[6] INTEGER[8] SEMI[15]
8行:BEGIN[0]
9行:FOR[4] ID[8] ASSIGN[10] NUMBER[13] TO[15] NUMBER[18] DO[21] BEGIN[24]
10行:ID[8] LPAREN[15] STRING[16] RPAREN[31] SEMI[32]
11行:END[4] SEMI[7]
12行:END[0] DOT[11]