状态机设计需确保每个状态明确输入指针停位:遇/先peek判注释或除号;字符串中后必消费下一字符并检查越界;操作符用两级if显式匹配,禁用自动合并;token切片优先string_view;数字字面量须按前缀手动分进制解析。

状态机怎么设计才不会漏掉转义字符和行注释边界
直接用 switch-case 套嵌套 if 容易在 \、" 和 // 结束位置丢掉下一个字符的消费,关键是要让每个状态明确“读完当前 token 后,输入指针停在哪”。比如遇到 / 后不能立刻进 COMMENT 状态,得先 peek 下一个字符:是 / 才进单行注释,是 * 才进多行,否则它就是除号。字符串字面量同理,进入 STRING 状态后,每次读到 就必须 consume 下一个字符(哪怕它是换行),否则 "a
b" 会被截断。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 所有状态函数返回
size_t(新位置索引),而非 void;主循环用pos = state_func(src, pos)更新 -
STRING状态中,遇到后必须检查pos + 1 ,越界就报 <code>Unterminated string literal - 注释状态里跳过所有非换行符,但遇到
就退出并返回该位置——这样下一轮循环能正确识别后续行首空格或预处理指令
如何区分 ==、= 和 => 这类重叠操作符
不能靠“最长匹配”就完事。C++ 词法规定 => 是 C++20 的三字符组(digraph),但实际 lexer 通常不展开它,而是作为独立 token 处理;而 == 和 = 是严格按前缀优先:读到 = 先记作潜在赋值,peek 下一个是 = 就升为相等,是 > 就可能是 =>(需结合上下文判断是否在 lambda 引出位置)。简单实现里,推荐只支持 ==、!=、、<code>>= 这类双字符比较符,把 => 当普通标识符或报错更安全。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 操作符状态用两级判断:
if (src[pos] == '=') { if (pos+1 - 避免写
else if (src[pos+1] == '>')——C++ 标准不把=>当词法单元,硬支持反而破坏语法分析器预期 - 所有双字符操作符必须显式列出,不要用 map 或正则自动合并,否则
!和!=会冲突
std::string_view 在 token 切片时为什么比 substr() 更合适
每次调用 std::string::substr() 都分配堆内存,对千级 token 的源文件就是上千次 malloc;而 std::string_view 只存指针+长度,切片开销是常数时间。但要注意:它不拥有数据,所以传入的源字符串生命周期必须长于所有 token 对象——这意味着你不能把 lexer 包装成临时对象然后返回一堆 string_view token,必须确保源字符串(如 std::string source = read_file(...))一直活着。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- token 结构体里字段用
std::string_view lexeme,构造时传source.substr(start, end-start)是错的,应传std::string_view(source).substr(start, end-start) - 如果要序列化 token(比如写日志),再调用
lexeme.to_string(),而不是一开始就转 - 调试时打印
lexeme.data()前先 assertlexeme.data() != nullptr,防止 view 悬空
数字字面量解析怎么避免 0123 被误判为十进制
C++ 词法规则里,以 0 开头且后跟数字是八进制(0123 == 83),以 0x 或 0X 开头是十六进制,0b 是二进制(C++14 起)。但很多简易 lexer 直接用 std::stoi(s, nullptr, 0),它会自动推导进制——问题在于,std::stoi("0123", ..., 0) 返回 123(十进制),不是标准行为。必须手动识别前缀,再调用对应进制的转换函数,否则生成的 AST 数值错误,后续语义检查全崩。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 先检查
src[pos]:是0再 peekpos+1,是x/X就走十六进制,是b/B就走二进制,是数字就走八进制,否则走十进制 - 八进制解析时,循环检查每位是否在
'0'–'7',遇到'8'或'9'立即报错Invalid octal digit - 用
std::strtoull(lexeme.data(), &end, base)替代stoi,它能通过end指针验证是否整个字符串都被消费,防0xGG这类非法输入静默截断
状态机最麻烦的从来不是分支数量,而是每个转移边上的“副作用控制”——读了几个字符、有没有回退、错误时指针停在哪。写完一个状态,拿 "/* comment */ int x = 42;" 和 ""hello\nworld"" 这类混合样例跑一遍,比看十页理论文档管用。


















