如何将字符串拆分为词法单元(lexeme)?C#现有实现问题求解
问题根因分析
- 为什么会跳过Sin参数内内容/运行报错
你当前的匹配逻辑存在2个致命错误:
- 操作符前缀匹配后,没有校验最终拼接的
currentOp是否真的存在于操作符列表中:比如匹配S1时,前缀S匹配到Sin的前缀,但拼接上1后无匹配前缀,此时得到的currentOp是S,而你的操作符列表里没有S,执行Single查询会直接抛出未找到序列的异常,如果你上层吞了异常就会出现后续内容被跳过的情况。 - 索引处理错误:当你匹配完数字后索引
i已经指向非数字字符,操作符匹配结束后又执行了i--,如果操作符匹配长度为1的话,下一轮循环i++会导致重复读取同一个字符,逻辑混乱。
- 为什么检测不到非法词法单元
你没有对匹配失败的情况做兜底判断:当匹配完数字后,当前字符既不能拼接为合法操作符,也不属于数字开头,就属于非法词法单元,你当前代码没有对这种场景做捕获。
修复&优化后的实现
前置优化点
- 提前把操作符按长度倒序排序,优先匹配长操作符(避免把
Sin拆成S/i/n单个字符) - 操作符查询用哈希集合做O(1)校验,避免每次遍历整个列表
- 增加非法字符的判断分支
// 提前准备排序后的操作符列表,不用每次循环遍历,优先匹配长词提升准确率 var sortedOps = operations.Select(x => x.Name).OrderByDescending(name => name.Length).ToList(); const string input = "(2+2)*3S1(2)+Sin(1+2/S1(2))"; var lexemes = new List<string>(); int i = 0; while (i < input.Length) { // 跳过空格(如果不需要支持空格输入可删除该段) if (char.IsWhiteSpace(input[i])) { i++; continue; } // 1. 先匹配数字 if (char.IsDigit(input[i]) || input[i] == '.') { var numBuilder = new StringBuilder(); while (i < input.Length && (char.IsDigit(input[i]) || input[i] == '.')) { numBuilder.Append(input[i]); i++; } lexemes.Add(numBuilder.ToString()); continue; } // 2. 匹配操作符/函数名 bool matchedOp = false; foreach (var op in sortedOps) { if (i + op.Length <= input.Length && input.Substring(i, op.Length) == op) { lexemes.Add(op); i += op.Length; matchedOp = true; break; } } if (matchedOp) { continue; } // 3. 既不是数字也不是合法操作符,抛出非法词法异常 throw new InvalidOperationException($"非法词法单元:位置 {i} 的 {input[i]} 开头的内容不属于合法操作符或数字"); } // 合法输入下可拿到最终词法数组 Console.WriteLine(string.Join(", ", lexemes));
运行效果
针对你给出的输入字符串,代码会在匹配到3后的S时抛出异常,提示S开头的内容不是合法词法单元,符合你的需求。如果把输入里的S1都替换为合法操作符,会正常输出拆分后的词法数组。
内容的提问来源于stack exchange,提问作者Denis Suleimanov
相关产品推荐
相关产品推荐

