Разграничение токенов, содержащих подстановочные знаки и альтернативные окончания, с помощью ANTLR - PullRequest
2 голосов
/ 22 марта 2012

Я должен написать парсер для унаследованного языка программирования, чтобы перевести его на другой.Операторы SQL могут быть встроены непосредственно в присваивания.

Поскольку мне не нужно фактически анализировать SQL, а просто передать его как строку в библиотечную функцию целевой среды, я хотел распознать операторы SQL как токены на уровне лексера, используя следующее правило.

 SqlStatement : SELECT .+ ';' ;

К сожалению, операторы sql могут быть или завершаться точкой с запятой или ключевым словом EXECUTING (который вводит блок команд, но это не имеет значения).

Я не могу просто определить другой токен как:

SqlAndExecute : SELECT .+ EXECUTING ;

Поскольку эти два перекрываются, и это приводит к тому, что ANTLR (на удивление?) Испускает ложный токен "ELECT".Даже если это сработало, я даже не могу написать что-то вроде

 SqlStatement : SELECT .+ ';' | EXECUTING;

, потому что мне нужно различать две формы.

Можно ли вообще получить этот результат?Я пытался написать синтаксические предикаты, но я, вероятно, все еще что-то упускаю.

Я бы предпочел избегать синтаксического анализа SQL-запросов, если это возможно.

NB: SELECT определяется как S E L E C Tс fragment S: 's'|'S' и т. д. для других букв в идентификаторе;аналогично для EXECUTING

1 Ответ

2 голосов
/ 22 марта 2012

Не используйте .+ ';' в этом случае: при этом вы не можете провести различие между ';' как концом оператора SQL и одним из строкового литерала.

Итак, проведите различие между SqlAndExecute и SqlStatement, вы просто сопоставите то, что имеют оба токена, а затем, в конце, измените тип токена следующим образом:

Sql
 : SELECT Space SqlAtom+ ( ';'       {$type=SqlStatement;}
                         | EXECUTING {$type=SqlAndExecute;}
                         )
 ;

fragment SqlStatement  : /* empty, used only for the token-type */ ;
fragment SqlAndExecute : /* empty, used only for the token-type */ ;

Теперь SqlAtom является либо строковым литералом, либо, , когда впереди EXECUTING нет , любой символ, кроме одинарных кавычек ('\'') или точки с запятой (* 1015) *). Часть ", когда впереди EXECUTING нет " - должна обрабатываться дополнительным предварительным просмотром вручную в лексере и семантическим предикатом .

Небольшая демонстрация:

grammar T;  

@lexer::members {

  private boolean aheadIgnoreCase(String text) {
    int i;

    for(i = 0; i < text.length(); i++) {

      String charAhead = String.valueOf((char)input.LA(i + 1));

      if(!charAhead.equalsIgnoreCase(String.valueOf(text.charAt(i)))) {
        return false;
      }
    }

    // there  can't be a letter after 'text', otherwise it would be an identifier
    return !Character.isLetter((char)input.LA(i + 1));
  }
}

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

Sql
 : SELECT SP SqlAtom+ ( ';'       {$type=SqlStatement;}
                      | EXECUTING {$type=SqlAndExecute;}
                      )
 ;

Space
 : SP+ {skip();}
 ;

Id
 : ('a'..'z' | 'A'..'Z')+
 ;

fragment SqlAtom
 : {!aheadIgnoreCase("executing")}?=> ~('\'' | ';')
 | Str
 ;

fragment Str : '\'' ('\'\'' | ~('\'' | '\r' | '\n'))* '\'';

fragment SELECT    : S E L E C T;
fragment EXECUTING : E X E C U T I N G;
fragment SP        : ' ' | '\t' | '\r' | '\n';

fragment C : 'c' | 'C';
fragment E : 'e' | 'E';
fragment G : 'g' | 'G';
fragment I : 'i' | 'I';
fragment L : 'l' | 'L';
fragment N : 'n' | 'N';
fragment S : 's' | 'S';
fragment T : 't' | 'T';
fragment U : 'u' | 'U';
fragment X : 'x' | 'X';

fragment SqlStatement  : ;
fragment SqlAndExecute : ;

А если вы сейчас проанализируете ввод:

Select bar from EXECUTINGIT EXECUTING
x
Select foo from EXECUTING
y
SELECT a FROM b WHERE c=';' and More;

на консоль будет выведено следующее:

SqlAndExecute  'Select bar from EXECUTINGIT EXECUTING'
Id             'x'
SqlAndExecute  'Select foo from EXECUTING'
Id             'y'
SqlStatement   'SELECT a FROM b WHERE c=';' and More;'

EDIT

Обратите внимание, что правило Sql теперь всегда создает токен SqlStatement или SqlAndExecute. Другими словами: токена Sql никогда не будет. Если вы хотите сопоставить SqlStatement или SqlAndExecute, создайте правило синтаксического анализатора, соответствующее одному из них:

sql
 : SqlStatement
 | SqlAndExecute
 ;

и используйте sql в правилах вашего парсера вместо Sql.

...