Как отличить очень похожий на ключевые слова токен от ключевого слова с помощью ANTLR? - PullRequest
1 голос
/ 04 августа 2011

Мне трудно отличить ключевое слово от не-ключевого слова, когда грамматика позволяет не-ключевому слову выглядеть так же, как и ключевое слово.

Вот грамматика:

grammar Query;

options {
  output = AST;
  backtrack = true;
}
tokens {
  DefaultBooleanNode;
}

// Parser

startExpression : expression EOF ;

expression : withinExpression ;

withinExpression
  : defaultBooleanExpression
    (WSLASH^ NUMBER defaultBooleanExpression)*

defaultBooleanExpression
  : (queryFragment   -> queryFragment)
    (e=queryFragment -> ^(DefaultBooleanNode $defaultBooleanExpression $e))*
  ;

queryFragment : unquotedQuery ;

unquotedQuery : UNQUOTED | NUMBER ;

// Lexer

WSLASH    : ('W'|'w') '/';

NUMBER    : Digit+ ('.' Digit+)? ;

UNQUOTED : UnquotedStartChar UnquotedChar* ;

fragment UnquotedStartChar
  : EscapeSequence
  | ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\'
     | ':' | '"' | '/' | '(' | ')' | '[' | ']'
     | '{' | '}' | '-' | '+' | '~' | '&' | '|'
     | '!' | '^' | '?' | '*' )
  ;

fragment UnquotedChar
  : EscapeSequence
  | ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\'
     | ':' | '"' | '(' | ')' | '[' | ']' | '{'
     | '}' | '~' | '&' | '|' | '!' | '^' | '?'
     | '*' )
  ;

fragment EscapeSequence
  : '\\'
    ( 'u' HexDigit HexDigit HexDigit HexDigit
    | ~( 'u' )
    )
  ;

fragment Digit : ('0'..'9') ;
fragment HexDigit : ('0'..'9' | 'a'..'f' | 'A'..'F') ;

WHITESPACE : ( ' ' | '\r' | '\t' | '\u000C' | '\n' ) { skip(); };

Я упростила это настолько, чтобы избавиться от отвлекающих факторов, но я думаю, что дальнейшее удаление устранит проблему.

  • Косая черта разрешена в середине фрагмента запроса без кавычек.
  • В частности, булевы запросы не содержат обязательного ключевого слова.
  • Вводится новый синтаксис (например, W / 3), но я стараюсь не влиять на существующие запросы, которые выглядят похожими (например, X / Y)
  • Поскольку '/' является действительным как часть слова, ANTLR, кажется, дает мне "W / 3" как один токен типа UNQUOTED вместо того, чтобы быть WSLASH, за которым следует NUMBER.
  • Из-за вышеизложенного я получаю дерево, подобное: DefaultBooleanNode (DefaultBooleanNode (~ первое предложение ~, "W / 3"), ~ второе предложение ~), тогда как я действительно хотел WSLASH (~ первое предложение ~ , "3", ~ второе предложение ~).

Что я хотел бы сделать, так это написать правило UNQUOTED как «то, что у меня есть сейчас, но не соответствует ~~~~», но я не знаю, как это сделать.

Я понимаю, что могу изложить это полностью, например ::1010 *

  • Любой символ из UnquotedStartChar, кроме 'w', за которым следует остальная часть правила
  • 'w', за которым следует любой символ из UnquotedChar, кроме '/', за которым следует остальная часть правила
  • 'w /', за которым следует любой символ из UnquotedChar, кроме цифр
  • ...

Однако это выглядело бы ужасно. :)

1 Ответ

3 голосов
/ 04 августа 2011

Когда лексер, сгенерированный ANTLR, «видит», что определенный вход может соответствовать более чем одному правилу, он выбирает самое длинное совпадение.Если вы хотите, чтобы более короткое совпадение имело приоритет, вам нужно объединить все похожие правила в одно, а затем проверить с помощью стробированного семантического предиката , если более короткое совпадение впереди или нет,Если более короткое совпадение впереди, вы меняете тип токена.

Демо:

Query.g

grammar Query;

tokens {
  WSlash;
}

@lexer::members {
  private boolean ahead(String text) {
    for(int i = 0; i < text.length(); i++) {
      if(input.LA(i + 1) != text.charAt(i)) {
        return false;
      }
    }
    return true;
  }
}

parse
  :  (t=. {System.out.printf("\%-10s \%s\n", tokenNames[$t.type], $t.text);} )* EOF
  ;

NUMBER
  :  Digit+ ('.' Digit+)? 
  ;

UNQUOTED 
  :  {ahead("W/")}?=> 'W/' { $type=WSlash; /* change the type of the token */ }
  |  {ahead("w/")}?=> 'w/' { $type=WSlash; /* change the type of the token */ }
  |  UnquotedStartChar UnquotedChar* 
  ;

fragment UnquotedStartChar
  :  EscapeSequence
  |  ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\'
      | ':' | '"' | '/' | '(' | ')' | '[' | ']'
      | '{' | '}' | '-' | '+' | '~' | '&' | '|'
      | '!' | '^' | '?' | '*' )
  ;

fragment UnquotedChar
  : EscapeSequence
  | ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\'
     | ':' | '"' | '(' | ')' | '[' | ']' | '{'
     | '}' | '~' | '&' | '|' | '!' | '^' | '?'
     | '*' )
  ;

fragment EscapeSequence
  :  '\\'
     ( 'u' HexDigit HexDigit HexDigit HexDigit
     | ~'u'
     )
  ;

fragment Digit    : '0'..'9';
fragment HexDigit : '0'..'9' | 'a'..'f' | 'A'..'F';

WHITESPACE : (' ' | '\r' | '\t' | '\u000C' | '\n') { skip(); };

Main.java

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    QueryLexer lexer = new QueryLexer(new ANTLRStringStream("P/3 W/3"));
    QueryParser parser = new QueryParser(new CommonTokenStream(lexer));
    parser.parse();
  }
}

Для запуска демонстрации в * nix / MacOS:

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

или в Windows:

java -cp antlr-3.3.jar org.antlr.Tool Query.g
javac -cp antlr-3.3.jar *.java
java -cp .;antlr-3.3.jar Main

, которая выведет следующее:

UNQUOTED   P/3
WSlash     W/
NUMBER     3

EDIT

Чтобы исключить предупреждение при использовании токена WSlash в правиле синтаксического анализатора, просто добавьте пустое правило фрагмента к вашей грамматике:

 fragment WSlash : /* empty */ ;

Это немного хак,но вот как это делается.Больше никаких предупреждений.

...