跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

C++ 实现简单的脚本词法分析状态机跳转 _ Token 分词实现逻辑【源码】

不能直接用if-else堆词法分析器,因状态多、边界杂、回退难;状态机显式建模各状态与转移,天然支持回退,且通过两阶段提交和字符分类查表兼顾清晰性与性能。 为什么不能直接用 if-else 堆出词法分析器 因为状态多、边界杂、回退难。比如识别
0x123
(十六进制整数)时,读到
'0'
后必须试探下一个字符是
'x'
还是
'0'–'9'
,再决定走整数还是浮点路径;若误判,还得把已读字符“吐回去”。纯 if-else 容易漏掉回退逻辑,或在嵌套条件里迷失控制流。 状态机把每个判断节点显式建模为状态,转移只依赖当前状态 + 当前字符,天然支持回退(比如在
STATE_IDENTIFIER
中遇到非法字符,就回退一个位置并输出 identifier token)。 状态定义建议用 enum:如
STATE_START
、
STATE_IN_NUMBER
、
STATE_IN_STRING
每个状态对应一个处理块,只关心“当前字符能带我到哪” 所有状态共享同一个
pos
指针,但只有确认终结态才推进
pos
;未终结时保持原位,留给上层决定是否回退 如何设计可终止的字符消费与 token 提交逻辑 关键不是“读完一个 token 就停”,而是“读到某个状态后,能明确回答:这是完整 token 吗?要不要回退?”——比如
123.456e+
是不完整浮点字面量,必须在
e
后检测下一个字符是否为数字或符号,否则就得截断为
123.456
并回退
e
的位置。 推荐用“两阶段提交”:先运行状态机到无法继续(或遇空白/分隔符),再根据最终状态决定 token 类型和长度: 立即学习 “ C++免费学习笔记(深入) ”; 若停在
STATE_IN_NUMBER
且下一个字符非数字/小数点/e/E/下划线 → 提交
TOKEN_NUMBER
,
pos
不动(即不消费分隔符) 若停在
STATE_IN_STRING
但没遇到结束引号 → 报错
"unclosed string literal"
,不提交 token 关键字(如
if
、
while
)必须在 identifier 状态结束后额外查表,避免把
ifdef
误判为
if
+
def
C++ 中怎么让状态跳转既清晰又不拖慢性能 别写 switch-case 套 switch-case,也别用 map 做动态分发。最简稳方案是二维数组驱动:第一维是状态,第二维是字符分类(如
CHAR_DIGIT
、
CHAR_LETTER
、
CHAR_SLASH
),查表得下一状态。 C知道 CSDN推出的一款AI技术问答工具 下载 字符分类函数示例:
int charClass(char c) { if (c >= '0' && c <= '9') return CHAR_DIGIT; if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_') return CHAR_LETTER; if (c == ' ' || c == ' ' || c == ' ' || c == ' ') return CHAR_WHITESPACE; if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%') return CHAR_OP; // ... 其他 return CHAR_OTHER; }
状态跳转表
next_state[STATE_MAX][CHAR_CLASS_MAX]
在编译期初始化,零开销 避免在循环内反复调用
std::isalpha
等 locale 敏感函数——它们可能触发锁或查表,比手工判断慢 3–5 倍 对 ASCII 范围字符(脚本语言词法基本够用),直接用
c & 0x80
判断是否 ASCII,再分支,比通用函数快 字符串和注释里的转义怎么不破坏状态机主线 转义不是独立状态,而是当前状态的“子模式”。比如在
STATE_IN_STRING
中,遇到
'\'
就临时切到
STATE_IN_ESCAPE
,只看下一个字符,合法则吞掉两个字符并回到
STATE_IN_STRING
,非法则报错。 单行注释同理:
//
触发
STATE_IN_LINE_COMMENT
,之后只等换行或 EOF,期间完全忽略所有字符(包括引号、反斜杠)。 不要在主状态里写
if (c == '\') { ... }
——这会让主逻辑膨胀且难测试 每个子状态(如
STATE_IN_ESCAPE
)应有明确退出条件,且退出后必须恢复原始状态,不能“卡住” 注意:Unicode 转义(如
u0041
)需额外计数,建议限制最大长度(如 4 位十六进制),超长直接报错,不尝试自动截断 真正麻烦的是嵌套注释(
/* ... */
)里出现
/*
或
*/
——必须严格按“最近匹配”规则,即
/* /* */ */
是合法嵌套,而状态机本身不处理嵌套深度,只靠计数器管理。这点容易被忽略,一不留神就提前终止注释。

相关文章