C 在Flex Lexer中,为什么在加载新输入之前将最后一个字符移动到缓冲区的开头?

C 在Flex Lexer中,为什么在加载新输入之前将最后一个字符移动到缓冲区的开头?,c,flex-lexer,lex,lexer,C,Flex Lexer,Lex,Lexer,我正在尝试理解一个Lexer(),我正在移植到JavaScript,我一直在理解如何将输入数据读入缓冲区。这是一个标准的Lexer,所以我希望有人能给我一些提示,告诉我发生了什么 有问题的片段: register char *dest = yy_current_buffer->yy_ch_buf; register char *source = yytext_ptr; ... /* First move last chars to start of buffer. */ number_t

我正在尝试理解一个Lexer(),我正在移植到JavaScript,我一直在理解如何将输入数据读入缓冲区。这是一个标准的Lexer,所以我希望有人能给我一些提示,告诉我发生了什么

有问题的片段:

register char *dest = yy_current_buffer->yy_ch_buf;
register char *source = yytext_ptr;
...

/* First move last chars to start of buffer. */
number_to_move = (int) (yy_c_buf_p - yytext_ptr) - 1;

for ( i = 0; i < number_to_move; ++i )
    *(dest++) = *(source++);
register char*dest=yy_current_buffer->yy_ch_buf;
寄存器字符*source=yytext\u ptr;
...
/*首先将最后一个字符移动到缓冲区的开始处*/
数字移动=(int)(yy_c_buf_p-yy文本ptr)-1;
对于(i=0;i
我不明白为什么最后的字符必须移动到缓冲区的开头。我认为如果我需要比最初分配的更多的空间,缓冲区将被扩展,那么为什么需要将最后的字符拖放到前面呢

另外,循环实际上没有考虑缓冲区的“当前位置”(我对
number\u to\u move
的解释)。如果我的缓冲区大小为10000,并且我位于位置2048,那么在加载更多数据之前循环2048x有什么作用?我还考虑到,如果缓冲指针
yy_c_buf_p
和输入指针
yytext_ptr
保持同步,
number_to_move
将始终为0。但是,唉,也许有人能告诉我这里到底发生了什么,循环到底做了什么

谢谢

为什么必须将最后的角色拖到前面

严格地说,这不是,但它节省了时间和空间。当扫描仪到达缓冲区末端时,缓冲区大致如下所示:

      already scanned tokens-----------------------Curr
      ^                                            ^    ^
      |                                            |    |
  yy_ch_buf                                   yytext    buf_p
    Current tokenNext token-----------------------------
      | |------------read from yyin--------------------|
      ^
  yy_ch_buf
    yytext
    buf_p
从缓冲区开始到
yytext_ptr
之间的所有内容都不再需要,复制它会浪费时间,保留它也会浪费空间,这在编写flex时非常重要。扫描器不需要重新分配(除非因为缓冲区已满而确实需要重新分配),而只需将部分扫描的令牌移到缓冲区的开头,并从输入中填充缓冲区的其余部分

。。。如果缓冲区指针yy_c_buf_p和输入指针yytext_ptr保持同步

这是两个不同的指针,它们仅在令牌的开头“同步”
yytext_ptr
(是
yytext
的内部名称)指向当前令牌的开头
yy_c_buf_p
指向当前令牌扫描中的当前位置

当检测到缓冲区结束时,
yy_c_buf_p
指向终止缓冲区的NUL后一个,因此
yy_c_buf_p-yytext_ptr-1
是当前令牌中已扫描的字符数,在上例中为4。(因此它是当前标记中的位置,而不是缓冲区中的位置。)下一步将是从输入中读取
缓冲区大小-数字\u以移动
字符,以便缓冲区现在看起来如下所示:

      already scanned tokens-----------------------Curr
      ^                                            ^    ^
      |                                            |    |
  yy_ch_buf                                   yytext    buf_p
    Current tokenNext token-----------------------------
      | |------------read from yyin--------------------|
      ^
  yy_ch_buf
    yytext
    buf_p
yytext
必须指向当前标记的开头,因为这是最终执行操作时
yytext
的预期值
yy_c_buf_p
始终指向要扫描的下一个字符,因此当到达令牌的末尾时,它将指向下一个令牌中的第一个字符。(在执行操作之前,该字符将被NUL覆盖,在开始下一次扫描之前,该字符将被还原。这是代码的另一部分,对于不使用NUL终止字符串的语言的端口来说可能不需要。)

在缓冲区被重新填充后,扫描指针被重新定位到令牌的开头,这似乎有些奇怪,因为这意味着整个令牌将被重新扫描。这与flex扫描仪识别缓冲区结尾的方式有关;总之,在执行重新填充代码时,上次扫描的真实字符的扫描仪状态已丢失。对于编写flex时通常可用的机器来说,保持旧扫描仪状态的成本被认为太高:这意味着在内部扫描循环中有一个额外的指针拷贝,而对于许多机器来说,由于没有额外的寄存器,拷贝必须到内存。由于重新扫描很少发生,并且由于普通令牌非常短,因此重新扫描部分令牌被认为(并经过测试)比支付保持状态可用的成本更便宜。在您的应用程序中,这种权衡是否正确,您必须自己决定,可能需要借助基准测试

检测缓冲区结束的机制也是
yy_c_buf_p
比部分扫描令牌的结束超出两个字节而不是一个字节的原因。(在flex生成的扫描程序的上下文中,这很好,因为flex确保缓冲区以两个NUL字节终止,而不仅仅是一个字节。)


注意:Flex将调整输入缓冲区的大小(如果需要较大的令牌),前提是您使用默认的
%p
设置。但是原来的lex使用了一个数组作为缓冲区(flex声明
%a
),无法调整大小;扫描程序只会在很长的令牌上失败。(因为这些不会发生在行为良好的代码中,所以这不是问题,尽管它会影响您扫描注释的方式,例如。)因此,向后移动当前标记是处理缓冲区结束的唯一方法

为什么必须将最后的角色拖到前面

严格地说,这不是,但它节省了时间和空间。当扫描仪到达缓冲区末端时,缓冲区大致如下所示:

      already scanned tokens-----------------------Curr
      ^                                            ^    ^
      |                                            |    |
  yy_ch_buf                                   yytext    buf_p
    Current tokenNext token-----------------------------
      | |------------read from yyin--------------------|
      ^
  yy_ch_buf
    yytext
    buf_p
从缓冲区开始到
yytext_ptr
之间的所有内容都不再需要,复制它会浪费时间,保留它也会浪费空间,这在编写flex时非常重要。而不是重新分配(除非