【问题标题】:Scanner (Lexing keywords with ANTLR)扫描仪(使用 ANTLR 对关键字进行词法分析)
【发布时间】:2011-09-02 12:35:43
【问题描述】:

我一直在为我的程序编写一个扫描器,大多数在线教程都包括一个解析器和一个扫描器。似乎不可能在不同时编写解析器的情况下编写词法分析器。我只是想生成令牌,而不是解释它们。我想识别 INT 标记、浮点标记和一些标记,如“开始”和“结束”

我对如何匹配关键字感到困惑。我尝试了以下方法失败:

KEYWORD : KEY1 | KEY2;

KEY1 : {input.LT(1).getText().equals("BEGIN")}? LETTER+ ;
KEY2 : {input.LT(1).getText().equals("END")}? LETTER+ ;

FLOATLITERAL_INTLITERAL
  : DIGIT+ 
  ( 
    { input.LA(2) != '.' }? => '.' DIGIT* { $type = FLOATLITERAL; }
    | { $type = INTLITERAL; }
  )
  | '.'  DIGIT+ {$type = FLOATLITERAL}
;

fragment LETTER : ('a'..'z' | 'A'..'Z');
fragment DIGIT  : ('0'..'9');

IDENTIFIER 
 : LETTER 
   | LETTER DIGIT (LETTER|DIGIT)+ 
   | LETTER LETTER (LETTER|DIGIT)*
 ;

WS  //Whitespace
  : (' ' | '\t' | '\n' | '\r' | '\f')+  {$channel = HIDDEN;}
;  

【问题讨论】:

    标签: compiler-construction antlr antlr3 lexer


    【解决方案1】:

    如果您只想要一个词法分析器,请以以下方式开始您的语法:

    lexer grammar FooLexer; // creates: FooLexer.java
    

    LT(int): Token 只能在解析器规则内使用(在 TokenStream 上)。在词法分析器规则中,您只能使用从IntStream 获取下一个int(字符)的LA(int): int。但是没有必要全部手动往前看。只需执行以下操作:

    lexer grammar FooLexer;
    
    BEGIN
      :  'BEGIN'
      ;
    
    END
      :  'END'
      ;
    
    FLOAT
      :  DIGIT+ '.' DIGIT+
      ;
    
    INT
      :  DIGIT+
      ;
    
    IDENTIFIER 
      :  LETTER (LETTER | DIGIT)*
      ;
    
    WS
      :  (' ' | '\t' | '\n' | '\r' | '\f')+  {$channel = HIDDEN;}
      ; 
    
    fragment LETTER : ('a'..'z' | 'A'..'Z');
    fragment DIGIT  : ('0'..'9');
    

    我认为没有必要创建一个名为KEYWORD 的标记来匹配所有关键字:您需要区分BEGINEND 标记,对吧?但如果你真的想要这个,只需这样做:

    KEYWORD
      :  'BEGIN'
      |  'END'
      ;
    

    并删除 BEGINEND 规则。只需确保在 IDENTIFIER 之前定义了 KEYWORD

    编辑

    使用以下类测试词法分析器:

    import org.antlr.runtime.*;
    
    public class Main {
      public static void main(String[] args) throws Exception {
        String src = "BEGIN END 3.14159 42 FOO";
        FooLexer lexer = new FooLexer(new ANTLRStringStream(src));
        while(true) {
          Token token = lexer.nextToken();
          if(token.getType() == FooLexer.EOF) {
            break;
          }
          System.out.println(token.getType() + " :: " + token.getText());
        }
      }
    }
    

    如果您生成词法分析器,请编译 .java 源文件并运行 Main 类,如下所示:

    java -cp antlr-3.3.jar org.antlr.Tool FooLexer.g
    javac -cp antlr-3.3.jar *.java
    java -cp .:antlr-3.3.jar Main
    

    以下输出将打印到控制台:

    4 :: BEGIN
    11 ::  
    5 :: END
    11 ::  
    7 :: 3.14159
    11 ::  
    8 :: 42
    11 ::  
    10 :: FOO
    

    【讨论】:

    • 在您的示例中,FLOAT 和 INT 不能同时被识别,因为它是一个模棱两可的情况。我会收到以下警告:多个令牌规则可以匹配诸如“'0'..'9''0'..'9'”之类的输入:FLOATLITERAL,INTLITERAL 因此,为此禁用了令牌INTLITERAL输入
    • @macneil,不,这不是真的。我的猜测是你没有复制粘贴我的建议。稍后我会添加一个小演示。
    • 如您所见,令牌3.1415942 属于不同类型(分别为FLOAT 和INT)。
    • 我正在使用类似的东西来调试我的词法分析器,作为一种生活质量,您可以将 println 更改为 System.out.println(FooLexer.tokenNames[token.getType()] + " :: " + token.getText()); 以获取令牌名称(至少在 ANTLR4 中)。
    【解决方案2】:

    [来自一个制作自定义词法分析器工具的人,并且仍在尝试学习 ANTLR]

    无聊的广泛回答:

    你是对的。许多书籍和课程混合了这两种工具。有时“生成/检测令牌”和“解释令牌”可能会混合使用。

    有时,开发人员试图做一个扫描仪,但仍然在脑海中混合扫描和解析;-)

    通常,在检测令牌时,您还必须执行一个操作(“解释”),就像将消息或找到的令牌打印到字符串一样简单。 示例:"{ cout

    还有一些情况可能会使该主题的初学者难以阅读。

    一种情况是多个文本可用于不同的标记。

    例子:

    “-”作为减法二元运算符,“-”作为负前缀运算符。 或者,将 5 视为整数和浮点数。在扫描器中,“-”可以被视为同一个标记,而在解析器中,您可以将其视为不同的标记。

    为了解决这个问题,我最喜欢的方法是在扫描/词法分析器过程中使用“通用标记”,然后在解析/语法过程中将它们转换为“自定义标记”。

    快速回答:

    前面的回答提到,先做一个语法,其实我建议在白板或者笔记本上试试,以后再用你最喜欢的(ANTLRL,other)扫描工具试试。

    考虑那些可能存在一些标记重叠的特殊情况。

    祝你好运。

    【讨论】:

      猜你喜欢
      • 2017-09-18
      • 1970-01-01
      • 2014-05-20
      • 1970-01-01
      • 2011-08-03
      • 1970-01-01
      • 1970-01-01
      • 2021-05-30
      • 1970-01-01
      相关资源
      最近更新 更多