将词素变体与Antlr3匹配

将词素变体与Antlr3匹配,antlr,antlr3,lexical-analysis,Antlr,Antlr3,Lexical Analysis,我正在尝试使用Antlr3.2和Java1.6在英语输入文本中匹配度量值。我有如下的词汇规则: fragment MILLIMETRE : 'millimetre' | 'millimetres' | 'millimeter' | 'millimeters' | 'mm' ; MEASUREMENT : MILLIMETRE | CENTIMETRE | ... ; tokens { T_MILLIMETRE; } fragm

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

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

MEASUREMENT
    :   MILLIMETRE | CENTIMETRE | ... ;
tokens {
    T_MILLIMETRE;
}

fragment
MILLIMETRE
    :   ('millimetre' | 'millimetres'
    |   'millimeter' | 'millimeters'
    |   'mm') { $type = T_MILLIMETRE; }
    ;
measurement
  :  Number m=MilliMeter {System.out.println($m.getType() == MeasurementParser.MilliMeter);}
  |  Number CentiMeter
  ;
我希望能够接受任何大小写输入的组合,更重要的是,只需为所有MM变量返回单个词汇标记。但目前,我的AST包含“毫米”、“毫米”、“毫米”等,就像在输入文本中一样

阅读之后,我想我需要做如下的事情:

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

MEASUREMENT
    :   MILLIMETRE | CENTIMETRE | ... ;
tokens {
    T_MILLIMETRE;
}

fragment
MILLIMETRE
    :   ('millimetre' | 'millimetres'
    |   'millimeter' | 'millimeters'
    |   'mm') { $type = T_MILLIMETRE; }
    ;
measurement
  :  Number m=MilliMeter {System.out.println($m.getType() == MeasurementParser.MilliMeter);}
  |  Number CentiMeter
  ;
但是,当我这样做时,在Antlr生成的Java代码中会出现以下编译器错误:

cannot find symbol
_type = T_MILLIMETRE;
我尝试了以下方法:

MEASUREMENT
    :   MILLIMETRE  { $type = T_MILLIMETRE; }
    |   ...
但是测量结果不再匹配

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

MEASUREMENT
    :   MILLIMETRE  -> ^(T_MILLIMETRE MILLIMETRE)
    |   ...
导致NPE:

java.lang.NullPointerException at org.antlr.grammar.v2.DefineGrammarItemsWalker.alternative(DefineGrammarItemsWalker.java:1555).
对解析器规则进行度量会给我带来可怕的“以下令牌定义永远无法匹配,因为先前的令牌匹配相同的输入”错误

通过创建解析器规则

measurement :  T_MILLIMETRE | ...
我得到警告“没有对应于令牌的lexer规则:T_毫米”。Antlr虽然运行,但它仍然以AST格式提供输入文本,而不是T_毫米

我显然还没有像Antlr那样看待世界。谁能给我一些提示或建议吗


史蒂夫

这里有一个方法:

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);
    }
}
它将生成以下点文件:

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"

}
它对应于树:

(由创建的图像)

编辑

请注意:

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

MEASUREMENT
    :   MILLIMETRE | CENTIMETRE | ... ;
tokens {
    T_MILLIMETRE;
}

fragment
MILLIMETRE
    :   ('millimetre' | 'millimetres'
    |   'millimeter' | 'millimeters'
    |   'mm') { $type = T_MILLIMETRE; }
    ;
measurement
  :  Number m=MilliMeter {System.out.println($m.getType() == MeasurementParser.MilliMeter);}
  |  Number CentiMeter
  ;

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

请注意,
fragment
规则仅在lexer中“活动”,在解析器中不存在。例如:

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

换句话说:
mm
类型不存在,它们都是
测量类型

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