我必须为遗留编程语言编写一个解析器,以将其转换为另一种语言。 SQL 语句可以直接嵌入到赋值中。
由于我不需要实际解析 SQL,而只需将其作为字符串传递给目标环境的库函数,因此我想使用以下规则将 SQL 语句识别为词法分析器级别的标记。
SqlStatement : SELECT .+ ';' ;
不幸的是,sql 语句可以由分号或关键字 EXECUTING 终止(它引入了命令 block ,但这不相关)。
我不能简单地将另一个标记定义为:
SqlAndExecute : SELECT .+ EXECUTING ;
由于两者重叠,这会导致 ANTLR(令人惊讶?)发出虚假的“ELECT” token 。
即使它有效,我也写不出这样的东西
SqlStatement : SELECT .+ ';' | EXECUTING;
因为我需要区分这两种形式。
我能得到这个结果吗?我尝试编写语法谓词,但我可能仍然遗漏了一些东西。
如果可能的话,我宁愿避免解析 SQL 查询。
注意:SELECT 被定义为 S E L E C T
,其中 片段 S: 's'|'S'
,对于标识符中的其他字母依此类推;与 EXECUTING 类似
最佳答案
在这种情况下不要使用 .+ ';'
:这样,您就无法区分 ';'
作为 SQL 语句的结尾和一个字符串文字内的一个。
因此,要区分 SqlAndExecute
和 SqlStatement
,您只需匹配两个 token 的共同点,然后在最后更改 token 的类型,例如这个:
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
时,可以是除单引号之外的任何字符 ( '\''
)或分号(';'
)。 “当前面没有 EXECUTING
”部分必须由词法分析器中的一些手动额外前瞻处理和 semantic predicate 处理。 .
快速演示:
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;'
编辑
请注意,Sql
规则现在始终生成 SqlStatement
或 SqlAndExecute
标记。换句话说:永远不会有 Sql
token 。如果您想要匹配 SqlStatement
或 SqlAndExecute
,请创建与其中之一匹配的解析器规则:
sql
: SqlStatement
| SqlAndExecute
;
并在解析器规则中使用 sql
而不是 Sql
。
关于antlr - 使用 ANTLR 分隔包含通配符和备用结尾的标记,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9823864/