我正在尝试为Antlr4中动态确定的批处理分隔符创建词法分析器规则 . 这支持两个用例:

  • 不同的数据库系统定义了自己的批处理分隔符(例如'go',';''/')

  • 我还想允许用户定义的批处理分隔符,最长可达2个字符,并且可能是任何东西,但是对于这个示例,我们假设它们是ascii字符 .

因此,出于本示例的目的,批处理分隔符是单独在其自身行上的任何字符串,并与当前已知的批处理分隔符匹配 . 还有几个更复杂的选择,但我想保持这个简单,因为问题是关于词法分析器中的动作和语义谓词,而不是批处理分隔符 .

因此,假设我已经定义了一个名为ALPHA的词法规则,它匹配任何大写或小写字母 . 另外,假设我只是尝试匹配 '\r'?'\n'<some-upto-two-char-string>'\r'?'\n' ,即它自己的行上的批处理分隔符,而没有其他空格来对抗

我定义了以下词法规则:

BATCH_SEPARATOR:
     NEWLINE ALPHA (ALPHA)? NEWLINE
    ;
    NEWLINE: '\r'?'\n';

此规则适用于大多数情况,但不考虑将输入批处理分隔符候选项与批处理分隔符的有效值动态匹配 . 因此,虽然它会成功地lex 'go',';'等,当它们作为 SELECTCREATE FUNCTION 语句的一部分出现在它们自己的行上时,它将错误地将'IN','AS'等作为批处理分隔符 .

所以现在我采取下一步检查实际的字符匹配,并在 @lexer::members 部分中定义一个名为 isValidBatchSeparator() 的方法 . 此方法基本上将已知的批处理分隔符(由应用程序 Build )与 _input.LA(-1)_input.LA(1) 进行比较,其类似于以下内容(在伪代码中):

private char[] _batchSeparator; // assume it is already set to some value and this array has at least size 1
public boolean isValidBatchSeparator()
{
   if batchSeparatorLength > 1
      if _batchSeparator[0] == (ignore case) _input.LA(-1) 
         && _batchSeparator[1] == (ignore case) _input.LA(1)
         return true
   else
    {
      if _batchSeparator[0] == (ignore case) _input.LA(-1)
      return true
    }
   return false

}

所以现在我把我的词法规则写成:

BATCH_SEPARATOR:
  NEWLINE BATCH_SEP_INNER NEWLINE
;
fragment BATCH_SEP_INNER:
  ALPHA (ALPHA)? {isValidBatchSeparator()}?
;

这看起来似乎正确 .

我能够逐步执行代码并验证语义谓词确实正在输入,并且该方法返回正确的值 . 但是,像 '\r\nGO\r\n' 这样的输入不会被视为 BATCH_SEPARATOR . 相反,在代码后面的某个地方,我有一个 IDENTIFIER 的定义,它通常将标识符定义为一串字符,当它从 BATCH_SEPARATOR 规则中删除时会捕获该字符串 . 显然,应用于片段的语义谓词与应用于非片段词法分析器规则的语义谓词不同 .

因此,我从词法分析器规则定义中删除片段并使 BATCH_SEP_INNER 成为一等公民,但是我的词法分析器语义谓词再次失败,即使语义谓词代码出现并返回正确的值,我仍然认为 '\r\nGO\r\n' lexed为 IDENTFIER (甚至没有 BATCH_SEP_INNER

我尝试了一些其他的东西,比如将语义谓词应用于 BATCH_SEPARATOR 而不是 BATCH_SEP_INNER . 这里的问题是 _input.LA(-1)_input.LA(1) 现在对应于 '\r''\n' 并且没有干净的方法来到实际代表批处理分隔符的ascii . 例如,在更复杂的情况下,也存在空白区域,或者在批处理分隔符ascii之前有几个 NEWLINE s .

因此,将此语义谓词应用于 BATCH_SEPARATOR 将始终无法匹配,并且我的输入字符串将无法正确获得lexed .

我还尝试将 isValidBatchSeparator() 吐出为两个,将其应用为将此方法的输出存储到变量中的操作,然后在应用于BATCH_SEPARATOR的语义谓词中使用该变量 . 像这样的东西:

BATCH_SEPARATOR:
 (NEWLINE BATCH_SEP_INNER NEWLINE) {_isValidBatchSeparator}?
;

fragment BATCH_SEP_INNER:
  {_isValidBatchSeparator = isValidBatchSeparator();}
  (ALPHA (ALPHA)?)
;

如果我这样做,我会收到一个警告,即片段中已经定义了一个动作,因此不会运行 . 显然,使它成为非片段会破坏它修复的更多东西,因为作为非片段规则, BATCH_SEP_INNER 匹配任何两个字符串并打破许多东西 .

所以作为最后的手段我尝试一些聪明的东西:

BATCH_SEPARATOR:
 (NEWLINE BATCH_SEP_INNER NEWLINE) {_isValidBatchSeparator}?
;

BATCH_SEP_INNER:
  {_isValidBatchSeparator = isValidBatchSeparator();}
  (ALPHA (ALPHA)?)
  {_isValidBatchSeparator}?
;

在最后一步中,我打算让 BATCH_SEP_INNER 运行操作,但是如果批处理分隔符无效,则在完成该操作后禁用词法分析器规则 . 然而,被翻译的Lexer实际上跳过了整个动作 . 我可以看到相应的代码生成,但代码路径永远不会遍历 .

所以现在我没有想法,正在寻找这个论坛寻求帮助:)