编译器构造java语言描述pdf_(基于Java)编写编译器和解释器-第3A章:基于Antlr构造词法分析器(连载)...

在上一章(第三章)中我们用纯手工的方式构造了一个Pascal的扫描器(也称词法分析器)。细心的读者会想到,大部分语言的词法构造过程都差不多,都有变量ID,字符串,整数,浮点数,关键字,特殊符号等(如比较符,赋值,索引括号)等等。事实上在编译技术发展到今天,手写词法分析器基本很少了,因为编程语言的词不同于自然语言,很容易通过机械的手段实现。有很多工具可以生成词法分析器,比如Flex,JavaCC等。不过在本书中,不管词法还是语法还是代码生成,我将使用Antlr(http://www.antlr.org)这个大名鼎鼎的工具来完成后续的代码自动化工作。关于Antlr的基础我就不介绍了,网上有很多的教程。这儿我推荐两个:《使用 Antlr 开发领域语言 - 开发一个完整的应用》,《The Definitive ANTLR Reference》

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 ;4 建立特殊符号列表:

1: PLUS : '+' ;2: MINUS : '-' ;3: STAR : '*' ;4: SLASH : '/' ;5: ASSIGN : ':=' ;6: COMMA : ',' ;7: SEMI : ';' ;8: COLON : ':' ;9: EQUAL : '=' ;10: NOT_EQUAL : '<>' ;11: LT : '=' ;14: GT : '>' ;15: LPAREN : '(' ;16: RPAREN : ')' ;17: LBRACK : '[' ;18: LBRACK2 : '(.' ;19: RBRACK : ']' ;20: RBRACK2 : '.)' ;21: POINTER : '^' ;22: AT : '@' ;23: DOT : '.' ;24: DOTDOT25: : '..' ;26: LCURLY : '{' ;27: RCURLY : '}' ;

5 上一章讲述了三种Token,单词Token,数字Token和字符串Token。对于单词Token来说,关键字已经在第三条列出,剩下的只是标识符即ID了。

标识符 Token:

ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*

表示由一个字母开头,后续是组成ID的字母和数字,还有下划线。

字符串Token:

STRING

: '\'' ('\'\'' | ~('\''))* '\'' ;

这里考虑到了两个连续单引号的问题。

数字Token:

1: NUMBER2: : ('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: fragment11: EXPONENT12: : ('e') ('+'|'-')? ('0'..'9')+13: ;

这个数字Token词法稍微复杂了点,但与PascalNumberToken里的extract方法基本一样,同样有整数部分,小数点,小数部分和指数部分。在第4行和第7行红色部分我们修改了Token的Type为预定义的NUMBER_REAL。NUMBER默认表示整数(Integer)。碰到小数点和(或)指数就表示实数(Real)了

6 空白和注释的处理

空白和注释在词法分析里面是要被“处理”掉的,也就是这个只有阅读上的意义,基本对语法和语义没影响。

空白:

1: WS : ( ' '2: | '\t'3: | '\f'4: | ( '\r\n'5: | '\r'6: | '\n'7: )8: {9: }10: )11: { $channel=HIDDEN; }12: ;

注意11行有一个$channel=HIDDEN的Antlr Action,这就是要被Antlr忽略掉的意思。

注释:

1: COMMENT2: : '{'3: (4: : '\r' '\n'5: | '\r'6: | '\n'7: | ~('}' | '\n' | '\r')8: )*9: '}'10: {$channel=HIDDEN;}11: ;

在2行有一个标明注释的大括号,第9行有一个结束的反大括号。注释里面包含了各种操作系统的换行符,比如 \r(苹果MAC),\n(类Unix),和\r\n(Win系列),所以这种注释是可以跨行的。

上一章中的Token常量token比如字符串和数字,我个人认为token只表示字面上的意思,即这样写对不对,至于逻辑(比如数字大小)是否正确,那应该是运行时的行为,还有好多编译优化都会讲常量放入常量池中,而不是每个都计算。这里虽然用了带value域的PascalAntlrToken,但我们不准备在词法阶段就将常量词的值计算出来,使用了一个占位方法ValueComputer.computerToken()来与前章意义统一。

7 错误恢复目前暂且不适用,到时候联合语法错误恢复一起。

8 运行源代码中的ShowToken类看输出效果,下面是从hello.pas文件中抽取的token。

--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]

每个token标识了它的类型,括号里面的数字表示它在当前行中的其实位置。

9 在Pascal.g文件中有@lex::members的类容,它表示覆盖lexer中token的输出。从默认的CommonToken改成继承自CommonTOken的PascalAntlrToken。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值