Go 的 regexp 默认 ^ 只匹配字符串开头,需用 (?m) 启用多行模式才能匹配每行首;空行和缩进需显式处理,如 ^[ \t]*#;复杂结构须结合状态机,strings.Index 适合简单检测,错误提示须精确定位行列。

为什么 regexp 匹配 Markdown 行首符号容易漏掉空行或缩进?
因为 Markdown 语法依赖行首位置,但 ^ 在 Go 的 regexp 中默认不识别多行模式下的每行起始——它只匹配整个字符串开头,除非显式启用 multiline 标志(即在正则前加 (?m))。
常见错误现象:^#\s+ 看似能抓标题,实际只匹配第一行;遇到带缩进的 # 不是标题 或空行后的新段落就失效。
- 必须用
(?m)^#\s+,否则^失效 - 空行会中断“行首”语义,需单独用
^\s*$捕获并跳过 - 制表符和全角空格容易被忽略,建议用
^[ \t]*#\s+显式覆盖 - 性能影响:开启
(?m)不影响速度,但过度使用.*回溯会导致 O(n²) 匹配延迟
如何让 regexp 区分代码块、引用块和普通段落?
靠单个正则无法可靠区分嵌套结构(比如代码块里的 >),必须按行扫描 + 状态机。正则只负责“行级初筛”,状态流转由 Go 代码控制。
使用场景:检查 ``` 是否成对、> 是否连续、列表项是否缩进一致。
立即学习“go语言免费学习笔记(深入)”;
- 代码块开始:匹配
^(?:`{3,}|~{3,})\s*(\w*)$,捕获语言标识(如go) - 引用块:用
^(?:>[ \t]*)+,注意要允许嵌套缩进(> > text) - 列表项:统一用
^[ \t]*(?:[-+*]|\d+\.)[ \t]+,避免把10. text和1. text当作不同层级 - 别用
.*跨行匹配代码块内容——交给状态变量inCodeBlock bool控制
strings.Index 比 regexp 更快,什么情况下该换?
当只需检测是否存在某个简单模式(如行首 #、是否含 http://),且不需要捕获分组或复杂边界时,strings.Index 是更轻量的选择。
性能差异明显:对 1KB 文本做 100 次标题检测,strings.Index 耗时约 0.02ms,regexp 约 0.15ms(含编译开销)。
- 判断是否为 H1:用
strings.HasPrefix(line, "# "),比regexp.MustCompile(`^#\s+`)直观又快 - 检查链接:用
strings.Contains(line, "http://") || strings.Contains(line, "https://"),避免写错\bhttps?://边界 - 但无法替代正则的场景:匹配“至少两个空格后的文字”(
\s{2,}(\w+))、提取邮箱(\b[\w.-]+@[\w.-]+\.\w+\b) - 注意:Go 的
strings不区分大小写,要查[Tt]odo还得回退到regexp
报错提示里为什么不能只写“语法错误”,而要定位到具体行和列?
因为用户改的是源文件,不是内存里的字符串切片。行号(lineNum)可直接跳转编辑器,列号(colOffset)能标出哪个字符触发了规则失效——比如 - item 缺少空格,问题就在第 3 个字符位置。
容易踩的坑:用 strings.Split(text, "\n") 后遍历切片,会丢失原始换行符长度(\r\n vs \n),导致列偏移计算错误。
- 正确做法:逐行扫描原字节流,用
bytes.IndexByte找换行符,累加偏移 - 报错格式统一用:
line 5: col 12: expected space after "-", got "a" - 别在错误信息里拼接整行内容——长行会刷屏,只给前后 10 字符上下文即可
- 如果某行超长(> 200 字符),列号可能不准,此时降级为“line 5: invalid list syntax”
事情说清了就结束










