本文是我和 deepseek 的一次对话的内容总结,即文案来自 AI。
以我正在开发的某工具的语法举例。
语境:
perl
# 某工具的语法文档
版本:0.0.1
## 基本信息
每级缩进为 2 个空格
## 语法细则
### 行
除空行外,这个实现提供 3 种行:链接行,注释行,要素行。
#### 链接行
语法:
```lore
[..indent][..link_name]: [..link_url]
```
#### 注释行
```lore
[..indent]# [..comment_content]
```
注释行不会被渲染。
#### 要素行
非空行的默认情况。
对于 html 目标,要素行会被渲染成
```html
<p>[..element_line_content]</p>
```
......
引言
在设计解析器或编译器时,最容易犯的错误不是"没实现功能",而是"没考虑到用户会怎么用错"。语法文档写得再清楚,用户总会想出你意想不到的写法。
本文提出一套系统化的方法,用于从最简语法文档推导出所有可能的意外情况,并以一个简单的配置文件格式为例,演示如何应用这套方法。
系统化地推导语法识别的意外情况,本质上是用工程思维对抗不确定性。通过拆解要素、枚举边界、组合测试,最大程度地覆盖用户可能犯的所有错误。
第一部分:核心方法论
1.1 基本思路
语法识别的意外情况推导可以归纳为四个步骤:
拆解语法要素 → 分析每个要素的维度 → 枚举边界条件 → 组合测试场景
1.2 需要关注的维度类型
| 维度类型 | 说明 | 例子 |
|---|---|---|
| 边界值 | 合法范围内的极端情况 | 空值、最大值、最小值 |
| 非法值 | 明显不符合语法的输入 | 未定义的符号、错误的格式 |
| 上下文干扰 | 在不同位置出现的相同符号 | 注释里的冒号、字符串里的井号 |
| 组合爆炸 | 多个边界条件同时出现 | 空行 + 特殊字符 + 缩进错误 |
第二部分:案例背景 ------ 一个简单的收藏夹格式
我们用一个简化的收藏夹语法作为案例。它将文本转换为 HTML 书签。
2.1 语法文档
版本:1.0
缩进:2个空格
行类型:
- 目录行 : 目录名
- 链接行 名称: URL
- 注释行 # 注释内容
- 文本行 普通文字(默认)
示例:
lore: 网站收藏夹 Google: https://google.com GitHub: https://github.com # ..其他书签
2.2 语法要素拆解
| 要素 | 符号/形式 | 作用 |
|---|---|---|
| 缩进 | 2个空格 | 表示层级关系 |
| 目录行 | : 开头 |
创建一个新目录 |
| 链接行 | 名称: URL 格式 |
添加一个书签链接 |
| 注释行 | # 开头 |
注释,不参与生成 |
| 文本行 | 无特殊符号 | 普通段落文字 |
| 空行 | 空 | 视觉分隔,不影响结构 |
第三部分:逐要素推导意外情况
3.1 缩进维度
3.1.1 边界条件分析
| 场景 | 示例 | 潜在问题 |
|---|---|---|
| 最小缩进(0) | : 编程 |
根目录正确 |
| 标准缩进(2) | Google: https://google.com |
标准子项 |
| 缩进递增(4) | : 子目录 |
深层嵌套 |
| 缩进递减(2→0) | : 子目录 : 另一目录 |
回到根目录是否允许 |
| 缩进不一致 | Google: ... Baidu: ... |
当前行比上一行多缩进2以上 |
3.1.2 意外情况列表
1. 缩进不是2的倍数
lore: 编程(3个空格)2. 缩进使用tab
lore: 编程3. 混合tab和空格
lore: 编程(空格) Google: https://google.com(tab)4. 空行有缩进
lore: 编程 Google: https://google.com5. 缩进越级(从0直接到4)
lore: 编程 Google: https://google.com6. 文件开头有缩进
lore: 编程
3.2 目录行维度(: 开头)
3.2.1 边界条件分析
| 场景 | 示例 | 潜在问题 |
|---|---|---|
| 标准目录 | : 编程 |
正常 |
| 空目录名 | : |
目录没有名字 |
| 多级目录名 | : 编程: 语言 |
冒号嵌套如何处理 |
| 特殊字符目录名 | : 编程&语言 |
特殊符号是否允许 |
3.2.2 意外情况列表
1. 冒号后有多个空格
lore: 编程2. 冒号前有空格
lore: 编程3. 多个连续冒号
lore:: 编程4. 目录名含冒号
lore: 编程:语言5. 空目录(有名字但无内容)
lore: 空目录6. 目录嵌套过深(性能边界)
lore: 1 : 2 : 3 ...(100层)
3.3 链接行维度(名称: URL 格式)
3.3.1 边界条件分析
| 部分 | 边界场景 | 示例 |
|---|---|---|
| 名称 | 空 | : https://google.com |
| 名称 | 含空格 | Google Search: https://google.com |
| 名称 | 含冒号 | Google:Search: https://google.com |
| URL | 空 | Google: |
| URL | 相对路径 | Google: /search |
| URL | 非法格式 | Google: not a url |
| URL | 特殊协议 | Google: javascript:alert(1) |
3.3.2 意外情况列表
1. 冒号前后无空格
loreGoogle:https://google.com
- 多个冒号
loreGoogle:Search: https://google.com
- URL含空格
loreGoogle: https://google.com
- URL含中文
lore百度: https://百度.com
- 名称含特殊符号
loreGoogle&Search: https://google.com
- 超长名称或URL
lore(5000字符)
3.4 注释行维度(# 开头)
3.4.1 边界条件分析
| 场景 | 示例 | 潜在问题 |
|---|---|---|
| 标准注释 | # 说明文字 |
正常 |
| 空注释 | # |
只有井号 |
| 注释含井号 | # 注释 # 嵌套 |
嵌套井号是否识别 |
| 注释含冒号 | # 注释: 内容 |
冒号是否被误解 |
3.4.2 意外情况列表
1. 注释行前有缩进
lore# 这是子注释2. 注释在行末
loreGoogle: https://google.com # 这是行末注释3. 连续注释
lore# 第一行 # 第二行 : 编程 :4. 注释与空行
lore# 注释 : 编程 # 空行后注释还属于编程吗?
3.5 文本行维度(默认类型)
3.5.1 边界条件分析
| 场景 | 示例 | 潜在问题 |
|---|---|---|
| 标准文本 | 普通文字 |
正常 |
| 空文本行 | (有缩进无内容) |
是空行还是文本行? |
| 文本含标识符 | Google: not a link |
被误认为链接行 |
| 文本含井号 | 文字 # 注释 |
井号是否开始注释 |
3.5.2 意外情况列表
1. 文本行以冒号开头
lore:这不是目录2. 文本行以井号开头但意图是文字
lore#这不是注释3. 文本行含URL但不符合链接格式
loreGoogle https://google.com4. 文本行为空(只有缩进)
lore
3.6 空行维度
3.6.1 边界条件分析
| 场景 | 示例 | 潜在问题 |
|---|---|---|
| 无空行 | 内容连续 | 正常 |
| 单空行 | 一行空 | 分隔作用 |
| 多空行 | 多个连续空行 | 是否合并处理 |
| 文件开头空行 | \n: 编程 |
是否忽略 |
| 文件结尾空行 | : 编程\n\n |
是否忽略 |
3.6.2 意外情况列表
1. 空行在缩进敏感位置
lore: 编程 Google: https://google.com # 空行后缩进是否重置?2. 只有空行的文件
lore
第四部分:组合测试(核心环节)
单个边界条件往往能被解析器处理,但多个边界条件的组合才是真正的挑战。
4.1 组合测试方法
css
组合测试用例 = 要素A的边界值 + 要素B的边界值 + 要素C的边界值
4.2 组合示例
组合1:空目录名 + 非法URL + 缩进错误
lore: Google: not a url组合2:特殊字符 + 空注释 + 深层嵌套
lore: 编程&语言 # : 子目录 Baidu: https://baidu.com组合3:行末注释 + URL含空格 + 混合缩进
loreGoogle Search: https://google com # 注释 : 另一个目录组合4:空文件 + 只有注释 + 结尾空行
lore# 这是一个空文件
4.3 需要重点测试的组合类型
| 组合类型 | 说明 | 示例场景 |
|---|---|---|
| 空值组合 | 多个要素同时为空 | 空目录 + 空链接 + 空注释 |
| 特殊字符组合 | 多个要素含特殊符号 | 目录名含& + 链接名含: + URL含空格 |
| 结构破坏组合 | 破坏层级结构 | 缩进错误 + 空行干扰 + 非法行类型 |
| 性能压力组合 | 极端值叠加 | 超长名称 + 深层嵌套 + 大量空行 |
第五部分:转化为测试用例
每个意外情况都应该对应至少一个测试用例。
5.1 测试用例格式
python
test_case = {
"name": "缩进不一致导致结构错误",
"input": """
: 编程
Google: https://google.com
Baidu: https://baidu.com
""",
"expected_behavior": "报告缩进错误,Baidu不应成为Google的子项",
"error_location": "第3行,缩进4个空格"
}
5.2 测试分类
| 测试类型 | 覆盖范围 |
|---|---|
| 单元测试 | 单个要素的边界条件 |
| 集成测试 | 要素之间的交互 |
| 压力测试 | 极端值组合 |
| 模糊测试 | 随机生成的非法输入 |
第六部分:推导清单模板
你可以用这个模板来系统化地推导任何语法的意外情况:
语法意外情况推导清单
markdown
1. 列出所有语法要素
要素1: ________
要素2: ________
要素3: ________
2. 对每个要素,枚举边界值
空值/缺失
最小值
最大值
特殊字符
非法格式
3. 对每个要素,枚举上下文干扰
在注释中出现
在字符串中出现
在非法位置出现
4. 生成组合测试
空值 × 空值
特殊字符 × 特殊字符
结构破坏 × 边界值
5. 转化为测试用例
每个意外情况一个用例
每个组合类型一个用例