zoukankan      html  css  js  c++  java
  • (基于Java)编写编译器和解释器第3A章:基于Antlr构造词法分析器(连载)

    在上一章(第三章)中我们用纯手工的方式构造了一个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    ;
    4 建立特殊符号列表:
       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'|'_')*
    表示由一个字母开头,后续是组成ID的字母和数字,还有下划线。
     
    字符串Token:
    STRING
    : '\'' ('\'\'' | ~('\''))* '\'' ;
    这里考虑到了两个连续单引号的问题。
     
    数字Token:
       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:     ;
    这个数字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: COMMENT
       2:         :  '{'
       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。
  • 相关阅读:
    P2813 母舰
    P5367 【模板】康托展开
    P1816 忠诚
    P3865 【模板】ST表
    P1913 L国的战斗之伞兵
    P4939 Agent2
    P1894 [USACO4.2]完美的牛栏The Perfect Stall
    P5017 摆渡车
    P1330 封锁阳光大学
    P5018 对称二叉树
  • 原文地址:https://www.cnblogs.com/lifesting/p/2595536.html
Copyright © 2011-2022 走看看