【问题标题】:Matching lexeme variants with Antlr3用 Antlr3 匹配词位变体
【发布时间】:2010-09-30 22:34:57
【问题描述】:

我正在尝试使用 Antlr 3.2 和 Java1.6 匹配英文输入文本中的测量值。我有如下的词汇规则:

fragment
MILLIMETRE
    :   'millimetre' | 'millimetres'
    |   'millimeter' | 'millimeters'
    |   'mm'
    ;

MEASUREMENT
    :   MILLIMETRE | CENTIMETRE | ... ;

我希望能够接受大小写输入的任意组合,更重要的是,只需为 MILLIMETRE 的所有变体返回一个词法标记。但目前,我的 AST 包含“毫米”、“毫米”、“毫米”等,就像在输入文本中一样。

看完http://www.antlr.org/wiki/pages/viewpage.action?pageId=1802308之后,我觉得我需要做如下的事情:

tokens {
    T_MILLIMETRE;
}

fragment
MILLIMETRE
    :   ('millimetre' | 'millimetres'
    |   'millimeter' | 'millimeters'
    |   'mm') { $type = T_MILLIMETRE; }
    ;

但是,当我这样做时,我在 Antlr 生成的 Java 代码中得到以下编译器错误:

cannot find symbol
_type = T_MILLIMETRE;

我尝试了以下方法:

MEASUREMENT
    :   MILLIMETRE  { $type = T_MILLIMETRE; }
    |   ...

但随后 MEASUREMENT 不再匹配。

带有重写规则的更明显的解决方案:

MEASUREMENT
    :   MILLIMETRE  -> ^(T_MILLIMETRE MILLIMETRE)
    |   ...

导致 NPE:

java.lang.NullPointerException at org.antlr.grammar.v2.DefineGrammarItemsWalker.alternative(DefineGrammarItemsWalker.java:1555).

将 MEASUREMENT 设置为解析器规则会给我带来可怕的“以下标记定义永远无法匹配,因为之前的标记匹配相同的输入”错误。

通过创建解析器规则

measurement :  T_MILLIMETRE | ...

我收到警告“没有对应于令牌的词法分析器规则:T_MILLIMETRE”。虽然 Antlr 运行,但它仍然给我 AST 中的输入文本,而不是 T_MILLIMETRE。

我显然还没有像 Antlr 那样看待这个世界。有人可以给我任何提示或建议吗?

史蒂夫

【问题讨论】:

    标签: antlr antlr3 lexical-analysis


    【解决方案1】:

    这是一种方法:

    grammar Measurement;
    
    options {
      output=AST;
    }
    
    tokens {
      ROOT;
      MM;
      CM;
    }
    
    parse
      :  measurement+ EOF -> ^(ROOT measurement+)
      ;
    
    measurement
      :  Number MilliMeter -> ^(MM Number)
      |  Number CentiMeter -> ^(CM Number)
      ;
    
    Number
      :  '0'..'9'+
      ;
    
    MilliMeter
      :  'millimetre'
      |  'millimetres'
      |  'millimeter'
      |  'millimeters'
      |  'mm'
      ;
    
    CentiMeter
      :  'centimetre'
      |  'centimetres'
      |  'centimeter'
      |  'centimeters'
      |  'cm'
      ;
    
    Space
      :  (' ' | '\t' | '\r' | '\n'){$channel=HIDDEN;}
      ;
    

    可以用下面的类进行测试:

    import org.antlr.runtime.*;
    import org.antlr.runtime.tree.*;
    import org.antlr.stringtemplate.*;
    
    public class Main {
        public static void main(String[] args) throws Exception {
            ANTLRStringStream in = new ANTLRStringStream("12 millimeters 3 mm 456 cm");
            MeasurementLexer lexer = new MeasurementLexer(in);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            MeasurementParser parser = new MeasurementParser(tokens);
            MeasurementParser.parse_return returnValue = parser.parse();
            CommonTree tree = (CommonTree)returnValue.getTree();
            DOTTreeGenerator gen = new DOTTreeGenerator();
            StringTemplate st = gen.toDOT(tree);
            System.out.println(st);
        }
    }
    

    生成以下 DOT 文件:

    digraph {
    
        ordering=out;
        ranksep=.4;
        bgcolor="lightgrey"; node [shape=box, fixedsize=false, fontsize=12, fontname="Helvetica-bold", fontcolor="blue"
            width=.25, height=.25, color="black", fillcolor="white", style="filled, solid, bold"];
        edge [arrowsize=.5, color="black", style="bold"]
    
      n0 [label="ROOT"];
      n1 [label="MM"];
      n1 [label="MM"];
      n2 [label="12"];
      n3 [label="MM"];
      n3 [label="MM"];
      n4 [label="3"];
      n5 [label="CM"];
      n5 [label="CM"];
      n6 [label="456"];
    
      n0 -> n1 // "ROOT" -> "MM"
      n1 -> n2 // "MM" -> "12"
      n0 -> n3 // "ROOT" -> "MM"
      n3 -> n4 // "MM" -> "3"
      n0 -> n5 // "ROOT" -> "CM"
      n5 -> n6 // "CM" -> "456"
    
    }
    

    对应于树:

    (图片由http://graph.gafol.net/创建)

    编辑

    请注意以下几点:

    measurement
      :  Number m=MilliMeter {System.out.println($m.getType() == MeasurementParser.MilliMeter);}
      |  Number CentiMeter
      ;
    

    将始终打印true,无论(毫米)标记的“内容”是mm、millimetre、millimetres、...

    【讨论】:

    • 感谢您的回复,巴特。我意识到了这种可能性。不同之处在于我试图在词汇层面解决问题,而您提出了句法规则。您的方式大概是正确的 Antlr 方式。我对这个问题的经验是,重写规则只适用于句法规则,而不适用于词法规则。我目前正在通过对 Java 代码中的结果进行后处理来解决我的解决方案中的问题,但我或许应该重新考虑我在 Antlr 中的词汇级别和句法级别所做的工作。
    • @Stephen,好吧,我明白你的意思了。但在我的示例中,类型(毫米)将始终为MilliMeter(参见我的EDIT)。所以我不完全确定你在追求什么。
    • 你让我思考,巴特。我以错误的方式处理问题。我试图通过使词法分析上下文敏感来有效地进行自下而上的识别。这意味着我很快就达到了 Antlr 的极限,因为它是一个自上而下的工具。我现在已经将很多分析转移到语法中(就像你的例子一样),一切都变得更容易了。我认为人们必须非常清楚 Antlr 中的词汇规则和句法规则之间的区别,即使它们看起来非常相似。并非语法规则可以做的所有事情都可以用词法规则来实现。
    • @Stephen,是的,非常正确。决定在词法分析器中放入什么以及在解析器中放入什么可能非常棘手,尤其是当语言变得更加复杂时。祝你好运!
    【解决方案2】:

    请注意,fragment 规则仅“存在”在词法分析器中,而不再存在于解析器中。例如:

    grammar Measurement;
    
    options {
      output=AST;
    }
    
    parse
      :  (m=MEASUREMENT {
           String contents = $m.text;
           boolean isMeasurementType = $m.getType() == MeasurementParser.MEASUREMENT;
           System.out.println("contents="+contents+", isMeasurementType="+isMeasurementType);
         })+ EOF
      ;
    
    MEASUREMENT
      :  MILLIMETRE
      ;
    
    fragment
    MILLIMETRE
      :  'millimetre' 
      |  'millimetres'
      |  'millimeter' 
      |  'millimeters'
      |  'mm'
      ;
    
    SPACE
      :  (' ' | '\t' | '\r' | '\n'){$channel=HIDDEN;}
      ;
    

    输入文本:

    "millimeters mm"
    

    将打印:

    contents=millimeters, isMeasurementType=true
    contents=mm, isMeasurementType=true
    

    换句话说:MILLIMETRE 类型不存在,它们都是MEASUREMENT 类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-17
      相关资源
      最近更新 更多