使用ANTLR分隔包含通配符和备用结尾的标记

时间:2012-03-22 14:05:11

标签: antlr lexer

我必须为遗留编程语言编写一个解析器,将其转换为另一种语言。 SQL语句可以直接嵌入到赋值中。

由于我不需要实际解析SQL,只是将其作为字符串传递给目标环境的库函数,我想使用以下规则将SQL语句识别为词法分析器级别的标记。

 SqlStatement : SELECT .+ ';' ;

不幸的是,sql语句可以 以分号或关键字EXECUTING(它引入一个命令块,但这不相关)终止

无法简单地将另一个令牌定义为:

SqlAndExecute : SELECT .+ EXECUTING ;

由于两者重叠,这导致ANTLR(令人惊讶地?)发出虚假的“ELECT”令牌。

即使它奏效了,我甚至都写不出像

这样的东西
 SqlStatement : SELECT .+ ';' | EXECUTING;

因为我需要区分这两种形式。

我可以得到这个结果吗?我试图写句法谓词,但我可能仍然遗漏了一些东西。

如果可能的话,我宁愿避免解析SQL查询。

注意:SELECT定义为S E L E C T fragment S: 's'|'S',依此类推标识符中的其他字母;类似于EXECUTING

1 个答案:

答案 0 :(得分:2)

在这种情况下不要使用.+ ';':因此,您无法区分';'作为SQL语句的结尾和字符串文字中的结尾。

因此,要区分SqlAndExecuteSqlStatement,您只需匹配两个令牌的共同点,然后最后更改令牌的类型,如下所示:

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规则现在始终生成SqlStatementSqlAndExecute令牌。换句话说:永远不会有Sql令牌。如果要匹配SqlStatementSqlAndExecute,请创建与其中一个匹配的解析器规则:

sql
 : SqlStatement
 | SqlAndExecute
 ;

并在解析器规则中使用sql而不是Sql