5个核心状态足够:STATE_START、STATE_IDENTIFIER、STATE_NUMBER、STATE_SLASH、STATE_COMMENT,分别处理标识符、数字、运算符、空白和单行注释;用string下标访问而非istream,避免EOF陷阱并支持安全回退。

直接用 switch + char 状态流转写词法分析器,比引入完整解析框架更轻、更可控——但状态跳转容易漏边角,Token 边界判断稍有偏差就会吞掉字符或无限循环。
怎么设计最小可行的状态机:5个核心状态够用
不需要一开始就覆盖所有 C 风格语法;先支撑标识符、数字、运算符、空白、单行注释这 5 类,就能跑通大多数简单脚本。每个状态只关心「当前字符能触发什么转移」,不预读也不回退。
-
STATE_START:遇到字母/下划线进STATE_IDENTIFIER,数字进STATE_NUMBER,/进STATE_SLASH,空白跳过,其他当单字符Token -
STATE_IDENTIFIER:持续收字母、数字、下划线;遇到非标识符字符(如+、;)就终止,把已读内容作为Token输出 -
STATE_NUMBER:收数字和小数点(但只允许一个),遇到非数字/点就停;注意123.是合法数字起点,但不能算完整Token -
STATE_SLASH:若下一个是/,切到STATE_COMMENT;否则退回一个字符,把/当除号Token -
STATE_COMMENT:吞掉直到换行或 EOF,不生成任何Token
为什么 input[i] 直接索引比 istream::get() 更稳
流式读取在换行、EOF、缓冲区边界处容易行为不一致,尤其调试时输入不规范。用 std::string 装源码后用下标访问,控制权完全在手。
- 避免
istream::peek()返回EOF时的负值陷阱,char类型可能被截断 - 可随时用
i--回退(比如/后不是/,就得把位置还回去) - 边界检查统一写成
i ,不用查 <code>good()/eof()/fail()多种状态 - 示例片段:
if (input[i] == '/' && i + 1 < input.size() && input[i + 1] == '/') { state = STATE_COMMENT; i++; }
Token 的起始与结束位置必须显式记录
只存 std::string 值会丢失原始位置信息,后续报错定位、语法高亮都无从下手。哪怕当前不用,字段也得预留。
立即学习“C++免费学习笔记(深入)”;
- 每个
Token至少含type(枚举)、lexeme(子串)、line、col -
col不是i,而是当前行内偏移:遇到\n就重置col = 0,每走一字符col++ - 构造
lexeme时不建议用substr(start, len)频繁分配,改用string_view(C++17)或传引用+长度参数 - 别在状态里拼接字符串——
STATE_IDENTIFIER中用start_pos = i记头,退出时再切片,避免中间分配
真正难的不是状态多,而是「某个字符该归给前一个 Token 还是开启下一个」——比如 123abc 应拆成数字 123 和标识符 abc,而不是卡死或吞掉 a。每次状态退出前,务必确认 i 指向的是未消费的第一个字符。


















