Не используйте .+ ';'
в этом случае: при этом вы не можете провести различие между ';'
как концом оператора 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
.