2016-10-17 3 views
1

Использование Antlr 4 У меня есть ситуация, я не уверен, как ее решить. Первоначально я задал вопрос на форуме https://groups.google.com/forum/#!topic/antlr-discussion/1yxxxAvU678 на дискуссионном форуме Antlr. Но этот форум, похоже, не получает большого трафика, поэтому я снова спрашиваю здесь.Семантически неоднозначный двусмысленный синтаксис

У меня есть следующая грамматика:

expression 
    : ... 
    | path 
    ; 

path 
    : ... 
    | dotIdentifierSequence 
    ; 

dotIdentifierSequence 
    : identifier (DOT identifier)* 
    ; 

Озабоченность здесь является то, что dotIdentifierSequence может означать несколько вещей семантически, и не все из них являются «дорожки». Но в настоящий момент все они распознаются как пути в дереве разбора, а затем мне нужно обрабатывать их специально у моего посетителя.

Но я бы очень хотел, это способ выразить dotIdentifierSequence обычаи, которые не являются путями к ним expression правило, а не в path правило, и до сих пор dotIdentifierSequence в пути обрабатывать пути использований.

Для того, чтобы быть ясно, A dotIdentifierSequence может быть любым из следующих:

  1. Путь - это SQL-подобный грамматика и выражение пути будет как таблицы или столбца ссылок в SQL, например, a.b.c
  2. Имя класса Java - например. com.acme.SomeJavaType
  3. Статическая ссылка на поле Java - например. com.acme.SomeJavaType.SOME_FIELD
  4. Ссылка на значение пересылки Java. com.acme.Gender.MALE

Идея заключается в том, что во время посещения «dotIdentifierSequence как путь» решает, как совершенно другой тип от других использований.

Любая идея, как я могу это сделать?

ответ

1

Проблема в том, что вы пытаетесь провести различие между «путями» при создании в парсере. Построив пути внутри лексером будет легче (псевдо-код следующим образом):

grammar T; 

tokens { 
    JAVA_TYPE_PATH, 
    JAVA_FIELD_PATH 
} 

// parser rules 

PATH 
: IDENTIFIER ('.' IDENTIFIER)* 
    { 
    String s = getText(); 
    if (s is a Java class) { 
     setType(JAVA_TYPE_PATH); 
    } else if (s is a Java field) { 
     setType(JAVA_FIELD_PATH); 
    } 
    } 
; 

fragment IDENTIFIER : [a-zA-Z_] [a-zA-Z_0-9]*; 

, а затем в синтаксический анализатор вы могли бы сделать:

expression 
: JAVA_TYPE_PATH #javaTypeExpression 
| JAVA_FIELD_PATH #javaFieldExpression 
| PATH    #pathExpression 
; 

Но, конечно же, вход как этот java./*comment*/lang.String будет лексемы ошибочно.

Обработка всего этого в синтаксическом анализаторе означала бы поиск в ручном режиме в потоке токенов и проверку наличия либо типа Java, либо поля.

Быстрый демо:

grammar T; 

@parser::members { 

    String getPathAhead() { 

    Token token = _input.LT(1); 

    if (token.getType() != IDENTIFIER) { 
     return null; 
    } 

    StringBuilder builder = new StringBuilder(token.getText()); 

    // Try to collect ('.' IDENTIFIER)* 
    for (int stepsAhead = 2; ; stepsAhead += 2) { 

     Token expectedDot = _input.LT(stepsAhead); 
     Token expectedIdentifier = _input.LT(stepsAhead + 1); 

     if (expectedDot.getType() != DOT || expectedIdentifier.getType() != IDENTIFIER) { 
     break; 
     } 

     builder.append('.').append(expectedIdentifier.getText()); 
    } 

    return builder.toString(); 
    } 

    boolean javaTypeAhead() { 

    String path = getPathAhead(); 

    if (path == null) { 
     return false; 
    } 

    try { 
     return Class.forName(path) != null; 
    } catch (Exception e) { 
     return false; 
    } 
    } 

    boolean javaFieldAhead() { 

    String path = getPathAhead(); 

    if (path == null || !path.contains(".")) { 
     return false; 
    } 

    int lastDot = path.lastIndexOf('.'); 
    String typeName = path.substring(0, lastDot); 
    String fieldName = path.substring(lastDot + 1); 

    try { 
     Class<?> clazz = Class.forName(typeName); 
     return clazz.getField(fieldName) != null; 
    } catch (Exception e) { 
     return false; 
    } 
    } 
} 

expression 
: {javaTypeAhead()}? path #javaTypeExpression 
| {javaFieldAhead()}? path #javaFieldExpression 
| path      #pathExpression 
; 

path 
: dotIdentifierSequence 
; 

dotIdentifierSequence 
: IDENTIFIER (DOT IDENTIFIER)* 
; 

IDENTIFIER 
: [a-zA-Z_] [a-zA-Z_0-9]* 
; 

DOT 
: '.' 
; 

, которые могут быть проверены с помощью следующего класса:

package tl.antlr4; 

import org.antlr.v4.runtime.ANTLRInputStream; 
import org.antlr.v4.runtime.CommonTokenStream; 
import org.antlr.v4.runtime.misc.NotNull; 
import org.antlr.v4.runtime.tree.ParseTreeWalker; 

public class Main { 

    public static void main(String[] args) { 

     String[] tests = { 
      "mu", 
      "tl.antlr4.The", 
      "java.lang.String", 
      "foo.bar.Baz", 
      "tl.antlr4.The.answer", 
      "tl.antlr4.The.ANSWER" 
     }; 

     for (String test : tests) { 
      TLexer lexer = new TLexer(new ANTLRInputStream(test)); 
      TParser parser = new TParser(new CommonTokenStream(lexer)); 
      ParseTreeWalker.DEFAULT.walk(new TestListener(), parser.expression()); 
     } 
    } 
} 

class TestListener extends TBaseListener { 

    @Override 
    public void enterJavaTypeExpression(@NotNull TParser.JavaTypeExpressionContext ctx) { 
     System.out.println("JavaTypeExpression -> " + ctx.getText()); 
    } 

    @Override 
    public void enterJavaFieldExpression(@NotNull TParser.JavaFieldExpressionContext ctx) { 
     System.out.println("JavaFieldExpression -> " + ctx.getText()); 
    } 

    @Override 
    public void enterPathExpression(@NotNull TParser.PathExpressionContext ctx) { 
     System.out.println("PathExpression  -> " + ctx.getText()); 
    } 
} 

class The { 
    public static final int ANSWER = 42; 
} 

, который будет печатать следующее в консоли:

PathExpression  -> mu 
JavaTypeExpression -> tl.antlr4.The 
JavaTypeExpression -> java.lang.String 
PathExpression  -> foo.bar.Baz 
PathExpression  -> tl.antlr4.The.answer 
JavaFieldExpression -> tl.antlr4.The.ANSWER 
+0

Ах, я никогда подумал о переносе этого слова в лексер. Хотя это имеет смысл, поскольку на самом деле все они представляют разные типы токенов. Спасибо за изобретательское решение! –

+0

Из вопроса я предположил, что OP уже выполняет такую ​​проверку, чтобы выяснить, является ли такой идентификатор ссылкой на путь или класс/тип и требует другого решения. Btw. с функциональной точки зрения, не имеет значения, если у вас есть эта проверка в синтаксическом анализаторе или в лексере, но наличие в лексере означает: ручное управление пробелами. –

Смежные вопросы