我正在尝试为 Antlr4 中动态确定的批处理分隔符创建词法分析器规则。这支持两个用例:
不同的数据库系统定义了自己的批处理分隔符(例如 'go'、';' '/')
我还希望允许用户定义的批处理分隔符最长为 2 个字符,并且可能是任何字符,但是对于这个示例,我们假设它们是 ascii 字符。
因此,就本示例而言,批处理分隔符是任何单独位于行中并与当前已知的批处理分隔符匹配的字符串。还有几个更复杂的替代方案,但我想保持简单,因为问题是关于词法分析器片段中的动作和语义谓词,而不是批处理分隔符。
所以假设我已经定义了一个名为 ALPHA 的词法分析器规则,它匹配任何大写或小写字母。另外,假设我只是想'\r'?'\n'<some-upto-two-char-string>'\r'?'\n'
在自己的行上匹配一个批处理分隔符,而没有其他空格可以抗衡
我定义了以下词法分析器规则:
BATCH_SEPARATOR:
NEWLINE ALPHA (ALPHA)? NEWLINE
;
NEWLINE: '\r'?'\n';
此规则适用于大多数情况,除了它不考虑将输入批处理分隔符候选者动态匹配到批处理分隔符的有效值。SELECT
因此,尽管它会成功地使用 lex 'go'、';' 等,但当它们作为orCREATE FUNCTION
语句的一部分出现在自己的行上时,它会错误地将 lex 'IN'、'AS' 等作为批处理分隔符。
所以现在我采取下一步检查实际的字符匹配并定义一个isValidBatchSeparator()
在 @lexer::members
section 中调用的方法。此方法本质上将已知的批处理分隔符(由应用程序建立)_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()}?
;
这似乎可以正确转换。
我能够单步执行代码并验证是否确实输入了语义谓词并且该方法返回了正确的值。但是,输入 like'\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)
and_input.LA(1)
现在对应于'\r'
and'\n'
并且没有干净的方法来获取实际代表批处理分隔符的 ascii。例如,在更复杂的情况下,也有空格或NEWLINE
批处理分隔符 ascii 之前有几个 s。
因此,应用此语义谓词BATCH_SEPARATOR
总是无法匹配,并且我的输入字符串将无法正确进行词法分析。
我还尝试一isValidBatchSeparator()
分为二,将其作为一个动作应用,将该方法的输出存储到一个变量中,然后在应用于 BATCH_SEPARATOR 的语义谓词中使用该变量。像这样的东西:
BATCH_SEPARATOR:
(NEWLINE BATCH_SEP_INNER NEWLINE) {_isValidBatchSeparator}?
;
fragment BATCH_SEP_INNER:
{_isValidBatchSeparator = isValidBatchSeparator();}
(ALPHA (ALPHA)?)
;
如果我这样做,我会收到一条警告,指出动作已在片段中定义,因此永远不会运行。显然,使其成为非片段会破坏它修复的更多问题,因为作为非片段规则,BATCH_SEP_INNER
匹配任何两个 char 字符串并破坏很多很多东西。
所以作为最后的手段,我尝试了一些聪明的方法:
BATCH_SEPARATOR:
(NEWLINE BATCH_SEP_INNER NEWLINE) {_isValidBatchSeparator}?
;
BATCH_SEP_INNER:
{_isValidBatchSeparator = isValidBatchSeparator();}
(ALPHA (ALPHA)?)
{_isValidBatchSeparator}?
;
在最后一步中,我打算BATCH_SEP_INNER
运行该操作,但如果批处理分隔符无效,则在完成此操作后禁用词法分析器规则。然而,转换后的 Lexer 实际上跳过了整个动作。我可以看到生成了相应的代码,但从未遍历代码路径。
所以现在我没有想法,正在寻找这个论坛寻求帮助:)