我正在尝试理解Lexer(source)我正在移植到JavaScript并且无法理解输入中的数据如何被读入缓冲区。这是一个标准的Lexer,所以我希望有人可以给我一些关于#919上发生的事情的提示。
相关代码段:
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++);
我不明白为什么必须将最后一个字符移动到缓冲区的开头。我认为如果我需要比最初分配的空间更多的空间,缓冲区将被扩展,那么为什么需要将最后一个字符拖到前面呢?
另外,循环并没有真正考虑缓冲区“当前位置”(我对number_to_move
的解释)。如果我的缓冲区大小是10000并且我在2048位置,那么在加载更多数据之前循环2048x的目的是什么?我还在考虑如果缓冲区指针yy_c_buf_p
和输入指针yytext_ptr
保持同步,number_to_move
总会最终为0.但是,唉,也许有人可以告诉我真的是什么发生在这里以及循环实际上做了什么。
谢谢!
答案 0 :(得分:3)
严格来说,它不是,但它节省了时间和空间。当扫描仪到达缓冲区的末尾时,缓冲区看起来大致如下:为什么最后一个角色的洗牌是必要的?
already scanned tokens-----------------------Curr
^ ^ ^
| | |
yy_ch_buf yytext buf_p
不再需要缓冲区开始和yytext_ptr
之间的所有内容,复制它会浪费时间,浪费空间来保持它,这在写入flex时很重要。而不是重新分配(除非因为缓冲区已满而非必要),扫描仪只需将部分扫描的标记移动到缓冲区的开头,并从输入中填充缓冲区的其余部分。
...如果缓冲区指针yy_c_buf_p和输入指针yytext_ptr保持同步......
这是两个不同的指针,它们只是&#34;同步&#34;在令牌的开头。 yytext_ptr
(这是yytext
的内部名称)指向当前令牌的开头; yy_c_buf_p
指向当前令牌扫描中的当前位置。
当检测到缓冲区结束时,yy_c_buf_p
指向NUL,终止缓冲区,因此yy_c_buf_p - yytext_ptr - 1
是在当前令牌中扫描的字符数,在上面的示例中为4 。 (所以它是当前标记中的位置,而不是缓冲区中的位置。)下一步是从输入中读取buffer_size - number_to_move
个字符,以便缓冲区现在看起来像这样:
Current tokenNext token-----------------------------
| |------------read from yyin--------------------|
^
yy_ch_buf
yytext
buf_p
yytext
必须指向当前令牌的开头,因为这是最终执行操作时yytext
的预期值。 yy_c_buf_p
始终指向要扫描的下一个字符,因此当真正到达令牌的末尾时,它指向下一个令牌中的第一个字符。 (在执行操作之前,该字符将被NUL覆盖,并且在开始下一次扫描之前,该字符将被恢复。这是代码的不同部分,并且可能不需要将端口转换为不使用的语言。 ; t使用NUL终止的字符串。)
在重新填充缓冲区后,将扫描指针重新定位到令牌的开头似乎很奇怪,因为这意味着将重新扫描整个令牌。这与flex扫描仪识别缓冲区结束的方式有关;总之,在执行重新填充代码时,扫描的最后一个真实字符的扫描仪状态已经丢失。保持旧扫描仪状态的成本被认为对于写入flex时通常可用的机器来说太高了:它将意味着内部扫描循环中的额外指针复制,并且对于许多机器来说,复制必须是记忆因为没有额外的寄存器。由于重新扫描很少发生,并且由于普通令牌非常短,因此重新扫描部分令牌被认为(并且被测试为)比支付保持状态可用的成本更便宜。在您的应用程序中,这种权衡是否正确是您必须自己决定的,可能需要借助基准测试。
检测缓冲区结束的机制也是yy_c_buf_p
超过部分扫描的令牌末尾两个字节而不是一个字节的原因。 (在flex生成的扫描程序的上下文中,这很好,因为flex确保缓冲区以两个NUL字节终止,而不仅仅是一个。)
注意:如果大型令牌需要,Flex会调整输入缓冲区的大小,假设您使用默认的%p
设置。但是原来的lex使用数组作为缓冲区(flex声明%a
),无法调整大小;扫描仪只会在很长的令牌上失败。 (因为那些没有发生在行为良好的代码中,这不是问题,例如,它会影响您扫描注释的方式。)因此,向后移动当前令牌是处理结束的唯一方法缓冲区。