回到 lex 中的旧位置

Going back to old position in lex

在我的 lex 处理过程中,我需要返回到 lex 输入文件,以使用不同的本地设置多次处理相同的输入。

但是,仅执行 fseek(yyin, old_pos, SEEK_SET); 是行不通的,因为输入数据由 lex 缓冲。我该如何(便携地)处理这个问题?

我试着在fseek()后面加了一个YY_FLUSH_BUFFER,但是没有用,因为旧的文件位置不正确(它被设置为填充缓冲区后的点,而不是到我评估令牌的地步)。

YY_FLUSH_BUFFER()fseek(yyin, position, SEEK_SET) 的组合(按任意顺序,但我会先执行 YY_FLUSH_BUFFER())肯定会导致从 [=15 开始扫描下一个标记=].问题是找出 position.

的正确值

跟踪字符偏移量相对简单(但如果您需要便携式扫描仪,它可以在 Windows 等非 Posix 平台上 运行,请参阅下面的免责声明) :

%{
  long scan_position = 0;
%}

%%
[[:space:]]*      scan_position += yyleng;
"some pattern"    { scan_position += yyleng; ... }

因为在每个规则中插入 scan_position += yyleng; 有点乏味,您可以使用 flex 有用的 YY_USER_ACTION 宏钩子:这个宏在每个动作(甚至是空动作)的开始处展开。所以你可以把上面的写得更简单:

%{
  long scan_position = 0;
  #define YY_USER_ACTION scan_position += yyleng;
%}

%%
[[:space:]]*      
"some pattern"    { ... }

一个警告:如果您使用任何调整令牌长度或以其他方式改变正常扫描过程的弹性操作,这将不起作用。这至少包括 yylessyymoreREJECTunputinput。如果您使用前三个中的任何一个,则需要重置 scan_position -= yyleng;(这需要在调用 yylessyymoreREJECT 之前进行)。对于 inputunput,您需要递增/递减 scan_position 以说明在扫描过程之外读取的字符。

免责声明:

这样的跟踪位置假定从输入流读取的字节与底层文件系统中的原始字节之间存在一对一的对应关系。对于 Posix 系统,保证是这种情况:fread(3)read(2) 将读取相同的字节并且 b 打开模式标志无效。

但一般来说,没有可靠的方法来跟踪文件位置。您可以以二进制模式打开流并自己处理系统的特殊行尾(这将适用于 Windows 但没有可移植的方式来确定行结束序列是什么,因此它也不是可移植的)。但在其他非 Posix 系统上,二进制读取可能会产生完全不同的结果(例如,底层文件可能使用固定长度的记录,以便每一行都被填充(使用一些系统特定的填充字符)使其成为正确的长度。

这就是 C 标准禁止使用计算 offset 值的原因:

For a text stream, either offset shall be zero, or offset shall be a value returned by an earlier successful call to the ftell function on a stream associated with the same file and whence shall be SEEK_SET. (§7.21.9.2 "The fseek function", paragraph 4).

无法在 flex 或我所知道的任何版本的 lex 中关闭缓冲,因为正确处理回退取决于能够缓冲。 (回退发生在扫描超出令牌末尾时,因为该令牌与恰好不存在的较长令牌的前缀相匹配。)

我认为唯一可移植的解决方案是将输入流令牌逐个复制到您自己的缓冲区(或临时文件)中,然后使用 yy_push_buffer_stateyy_scan_buffer(如果您正在使用一个缓冲区)将该缓冲区插入到输入流中。该解决方案看起来很像上面的跟踪代码,只是 YY_USER_ACTION 会将读取的标记附加到您自己的字符串缓冲区或临时文件中。 (您可能希望以标志为条件,以便它只发生在您要重新扫描的文件段中。)如果您有嵌套的重复,您可以在自己的 buffer/file 中跟踪位置,以便能return就可以了。