1、为什么需要输入缓冲?
-
词法分析器在识别标记时经常需要向前查看(lookahead)一个或多个字符才能确定当前标记的结束位置。
例如:
标识符识别:读到
count后需要看到空格或运算符才能确定标识符结束多字符运算符:在C语言中:
-可能是->的一部分
=可能是==的一部分
<可能是<=的一部分
-
效率问题:逐个字符读取效率低下
-
回退需求:确定标记边界后需要回退指针
2、缓冲对(Buffer Pairs)技术
2.1 双缓冲区设计
[缓冲区1: N字节] [缓冲区2: N字节]
-
缓冲区大小:通常为磁盘块大小(如4096字节)
-
设计原理:利用系统调用批量读取,减少I/O开销
2.2 关键指针机制
输入示例: E = M * C ^ 2 eof ↑ ↑ lexemeBegin forward
两个核心指针:
-
lexemeBegin指针:标记当前词素的开始位置
-
forward指针:向前扫描直到找到模式匹配
2.3 工作流程
初始化: lexemeBegin和forward都指向输入开始 循环执行: 1. forward向前移动,检查字符 2. 当确定标记边界时: - 记录lexemeBegin到forward之间的词素 - lexemeBegin移动到forward位置 - 返回标记给解析器 3. 缓冲区管理: - 当forward到达缓冲区末端时 - 加载另一个缓冲区 - forward移动到新缓冲区开始
2.2.4 缓冲区切换算法
// 伪代码示例
while (有输入字符) {
if (forward到达缓冲区1末端) {
加载缓冲区2;
forward = 缓冲区2起始位置;
}
else if (forward到达缓冲区2末端) {
加载缓冲区1;
forward = 缓冲区1起始位置;
}
// 处理当前字符
processCharacter(*forward);
forward++;
}
2.3 哨兵(Sentinels)优化技术
2.3.1 问题:频繁的缓冲区末端检查
原始方案需要对每个字符进行两次测试:
-
是否到达缓冲区末端?
-
当前字符是什么?
2.3.2 哨兵解决方案
在每个缓冲区末尾添加特殊字符(哨兵),将两个测试合并为一个。
哨兵字符选择:使用eof(文件结束符)
2.3.3 带哨兵的缓冲区结构
[正常字符...][eof哨兵] [正常字符...][eof哨兵]
2.3.4 优化后的算法
switch (*forward++) {
case eof:
if (forward在缓冲区1末端) {
加载缓冲区2;
forward = 缓冲区2起始;
}
else if (forward在缓冲区2末端) {
加载缓冲区1;
forward = 缓冲区1起始;
}
else {
// 真正的输入结束
终止词法分析;
}
break;
// 其他字符处理...
case '<': 处理小于运算符; break;
case '=': 处理等于运算符; break;
// ...
}
2.4 关键技术细节
2.4.1 缓冲区大小保证
重要定理:只要词素长度加上向前查看距离不超过N,就永远不会在确定词素前覆盖缓冲区。
数学表达:lexeme长度 + 向前查看距离 ≤ N
2.4.2 实际应用考虑
-
现代语言:大多数标记很短,1-2字符向前查看足够
-
长字符串处理:对于跨多行的长字符串,采用分段处理策略
-
Java示例:使用
+运算符连接多行字符串片段
2.4.3 错误处理集成
// 集成错误处理的完整算法
char* forward = input_buffer;
char* lexemeBegin = forward;
while (*forward != EOF) {
switch (*forward) {
case eof:
// 缓冲区管理逻辑
handleBufferEnd();
break;
case '<':
// 可能需要向前查看
if (*(forward+1) == '=') {
// 识别为<=运算符
returnToken(RELOP_LE);
forward += 2;
} else {
// 识别为<运算符
returnToken(RELOP_LT);
forward++;
}
lexemeBegin = forward;
break;
// 其他字符处理...
}
}
1480

被折叠的 条评论
为什么被折叠?



