1

我正在研究 ANTLR 中的 SQL 语法,它允许带引号的标识符(表名、字段名等)以及带引号的文字字符串。

问题是这个语法似乎总是将引用的输入匹配为“QUOTED_LITERAL”,而不是作为包含在引号中的 ID。

这是我的结果:

  • 输入:'blahblah' 结果:string_literal 符合预期。
  • 输入:field1 restul:column_name 符合预期
  • 输入:table.field1 结果:column_spec 符合预期
  • 输入:'table'.'field1' 结果:string_literal,MissingTokenException

下面是我对 SQL 语法的表达式部分的简化语法,如果有人可以帮助确定匹配引用规则而不是引用文字所需的内容,谢谢。

grammar test;

 expression
    :
    simpleExpression  EOF!
    ;

simpleExpression
    :
     column_spec
    | literal_value 
;
column_spec
    :
    (table_name '.')? column_name
    | ('\''table_name '\'''.')? '\'' column_name '\''
    | ('\"'table_name '\"' '.')? '\"' column_name '\"'
    ;

string_literal:     QUOTED_LITERAL ;
boolean_literal:    'TRUE' | 'FALSE' ;
literal_value :
    ( 
    string_literal  
    | boolean_literal 
    )   
        ;

table_name :ID;
column_name  :ID;

QUOTED_LITERAL:
    (  '\'' 
        ( ('\\' '\\') | ('\'' '\'') | ('\\' '\'') | ~('\'') )* 
    '\''  )
    |
    (  '\"'
        ( ('\\' '\\') | ('\"' '\"') | ('\\' '\"') | ~('\"') )* 
    '\"'  ) 
;

ID
    :   
    ( 'A'..'Z' | 'a'..'z' ) ( 'A'..'Z' | 'a'..'z' | '_'  | '0'..'9'| '::' )*
    ;


WHITE_SPACE : ( ' '|'\r'|'\t'|'\n' ) {$channel=HIDDEN;} ;
4

3 回答 3

1

如果有人感兴趣,我从引用的文字字符串中删除了一点灵活性。文字字符串只能用单引号引起来,标识符可以选择用双引号引起来。只要文字引号和标识符引号定义明确并且它们不重叠,语法就很简单。

此策略使语法更加简洁,并且不会删除引用标识符的能力。我使用 JDBC 方法 getIdentifierQuote 来报告可以使用哪个引号来包装标识符。

于 2013-02-01T14:38:50.137 回答
0

这是您的经典移位/减少冲突。(除了 ANTLR 不移位或减少;因为它不是堆栈自动机。)

您有以下问题:

当您处于 simpleExpression 状态时,您需要使用一个标记前瞻来决定要采用哪个分支。在 ANTLR 的情况下,由于词法分析器和解析器之间没有区别,因此一个标记是单个字符。(您应该会看到来自 ANTLR 的有关冲突的警告。)

它变得更好了,“Bob Dillan”和“table1”有什么区别?从解析器的角度来看,没有。那么,您希望如何在以下方面有所作为:

('\"'table_name '\"' '.')? '\"' column_name '\"'

(  '\"'
    ( ('\\' '\\') | ('\"' '\"') | ('\\' '\"') | ~('\"') )* 
'\"'  )

我强烈建议将simpleExpression规则重写为:

simpleExpression: 
    IDENTIFIER |
    IDENTIFIER . IDENTIFIER |
    QUOTED_LITERAL |
    QUOTED_LITERAL . QUOTED_LITERAL |
    boolean_literal;

然后在 simpleExpression 的动作代码中决定要做什么。特别是因为我很确定您可以引用带引号的表;“users”和“Bod Dillan”在句法上总是相同的。

这也取决于更高级的语法,你也可以在更高的层次上解决友好。

于 2013-01-24T16:06:59.610 回答
0

antlr 词法分析器是贪心的,因为当有两个可能的标记匹配时,它会匹配最长的一个。

当词法分析器看到“some_id”时,它可以将第一个引号匹配为一个引号,或者一个带引号的文字。文字较长,因此匹配。

作为旁注,您通常不希望词法分析器规则不匹配任何内容(如 ID)或在解析器规则中使用字符串常量,而仅引用标记名称。

你想做的是这样的。

QUOTE: '\'';
ID: ('a'..'z' | 'A'..'Z')+; // Must have at least one character
QUOTED_LITERAL: QUOTE ( (ID QUOTE) => { $type=QUOTE; } ) | .* QUOTE;

id: ID | QUOTE ID QUOTE;
quoted_literal: QUOTED_LITERAL | QUOTE ID QUOTE;

如果词法分析器看到看起来像带引号的 id 的东西,它无法判断使用哪个,因此它将其分解为更小的标记。在您的解析器中,您在期望可能引用 ID 的地方使用 id,在期望 QUOTED_LITERAL 的地方使用quoted_literal。

当输入不明确时,QUOTED_LITERAL 中的语法谓词会阻止它匹配完整的引号。

看起来这个,它将无法正确解析像

'tag' text 'second'

因为“文本”将被解析为 QUOTED_LITERAL。如果这是一个有效的输入,那么你需要类似的东西

fragment QUOTED_ID;
QUOTED_LITERAL: QUOTE ( ID {$type=QUOTED_ID} | .* ) QUOTE;
id: ID | QUOTED_ID;
quoted_literal: QUOTED_LITERAL | QUOTED_ID;

(我的示例并未涵盖您输入中的所有情况,但扩展它应该是显而易见的。您可能还需要一些操作来在您的 AST 中生成正确的标记或从文本中添加/删除引号,这取决于您之后执行的操作你解析。)

于 2013-01-24T16:14:36.740 回答