Сопоставление вариантов лексем с Antlr3

Я пытаюсь сопоставить измерения в тексте ввода на английском языке, используя 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; }
    ;

Однако, когда я это делаю, я получаю следующие ошибки компилятора в коде Java, сгенерированном Antlr:

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 в правило синтаксического анализатора дает мне ужасную ошибку «Следующие определения токенов никогда не могут быть сопоставлены, потому что предыдущие токены соответствуют одному и тому же входу».

Создав правило парсера

measurement :  T_MILLIMETRE | ...

Я получаю предупреждение «нет правила лексера, соответствующего токену: T_MILLIMETRE». Хотя Antlr работает, но он по-прежнему дает мне входной текст в AST, а не T_MILLIMETRE.

Очевидно, я еще не вижу мир так, как Antlr. Может ли кто-нибудь дать мне какие-либо советы или советы, пожалуйста?

Стив


person Stephen Winnall    schedule 29.09.2010    source источник


Ответы (2)


Вот как это сделать:

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,...

person Bart Kiers    schedule 29.09.2010
comment
Спасибо за ответ, Барт. Я знал об этой возможности. Разница в том, что я пытаюсь решить проблему на лексическом уровне, а вы предлагаете синтаксическое правило. Ваш путь, по-видимому, является правильным способом Antlr. Мой опыт решения этой проблемы заключается в том, что правила перезаписи работают только с синтаксическими правилами, а не с лексическими. В настоящее время я решаю проблему в своем решении путем постобработки результатов в моем Java-коде, но, возможно, мне следует пересмотреть то, что я делаю в Antlr на лексическом уровне и что я делаю на синтаксическом уровне. - person Stephen Winnall; 30.09.2010
comment
@Стивен, а, ладно, я понимаю, что ты имеешь в виду. Но в моем примере тип (для миллиметра) всегда будет MilliMeter (см. мой EDIT). Так что я не совсем уверен, что вам нужно. - person Bart Kiers; 30.09.2010
comment
Ты заставил меня задуматься, Барт. Я неправильно подходил к проблеме. Я пытался сделать эффективное восходящее распознавание, сделав лексический анализ контекстно-зависимым. Это означало, что я быстро достиг предела возможностей Antlr, так как это инструмент сверху вниз. Сейчас я перенес большую часть анализа в синтаксис (как в вашем примере), и все становится проще. Я думаю, нужно хорошо понимать разницу между лексическими и синтаксическими правилами в Antlr, даже если они очень похожи. Не все, что могут сделать синтаксические правила, возможно с помощью лексических. - person Stephen Winnall; 01.10.2010
comment
@Стивен, да, очень верно. Может быть довольно сложно решить, что поместить в лексер, а что в синтаксический анализатор, особенно когда язык становится более сложным. Удачи! - person Bart Kiers; 01.10.2010

Обратите внимание, что правила 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.

person Bart Kiers    schedule 30.09.2010