在上一章(第三章)中我们用纯手工的方式构造了一个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]
浙公网安备 33010602011771号