如何系统地推导语法系统的意外情况

6 阅读7分钟

本文是我和 deepseek 的一次对话的内容总结,即文案来自 AI。

以我正在开发的某工具的语法举例。

语境:

# 某工具的语法文档

版本: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
  • 注释行 # 注释内容
  • 文本行 普通文字(默认)

示例:

: 网站收藏夹
  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的倍数

   : 编程(3个空格)

2. 缩进使用tab

	: 编程

3. 混合tab和空格

  : 编程(空格)
	Google: https://google.com(tab)

4. 空行有缩进

  : 编程
    
  Google: https://google.com

5. 缩进越级(从0直接到4)

: 编程
    Google: https://google.com

6. 文件开头有缩进

  : 编程

3.2 目录行维度(: 开头)

3.2.1 边界条件分析

场景示例潜在问题
标准目录: 编程正常
空目录名:目录没有名字
多级目录名: 编程: 语言冒号嵌套如何处理
特殊字符目录名: 编程&语言特殊符号是否允许

3.2.2 意外情况列表

1. 冒号后有多个空格

:  编程

2. 冒号前有空格

 : 编程

3. 多个连续冒号

:: 编程

4. 目录名含冒号

: 编程:语言

5. 空目录(有名字但无内容)

: 空目录

6. 目录嵌套过深(性能边界)

: 1
  : 2
    : 3
      ...(100层)

3.3 链接行维度(名称: URL 格式)

3.3.1 边界条件分析

部分边界场景示例
名称: https://google.com
名称含空格Google Search: https://google.com
名称含冒号Google:Search: https://google.com
URLGoogle:
URL相对路径Google: /search
URL非法格式Google: not a url
URL特殊协议Google: javascript:alert(1)

3.3.2 意外情况列表

1. 冒号前后无空格

Google:https://google.com
  1. 多个冒号
Google:Search: https://google.com
  1. URL含空格
Google: https://google.com
  1. URL含中文
百度: https://百度.com
  1. 名称含特殊符号
Google&Search: https://google.com
  1. 超长名称或URL
(5000字符)

3.4 注释行维度(# 开头)

3.4.1 边界条件分析

场景示例潜在问题
标准注释# 说明文字正常
空注释#只有井号
注释含井号# 注释 # 嵌套嵌套井号是否识别
注释含冒号# 注释: 内容冒号是否被误解

3.4.2 意外情况列表

1. 注释行前有缩进

  # 这是子注释

2. 注释在行末

Google: https://google.com # 这是行末注释

3. 连续注释

# 第一行
# 第二行
: 编程
: 

4. 注释与空行

# 注释

: 编程  # 空行后注释还属于编程吗?

3.5 文本行维度(默认类型)

3.5.1 边界条件分析

场景示例潜在问题
标准文本普通文字正常
空文本行 (有缩进无内容)是空行还是文本行?
文本含标识符Google: not a link被误认为链接行
文本含井号文字 # 注释井号是否开始注释

3.5.2 意外情况列表

1. 文本行以冒号开头

:这不是目录

2. 文本行以井号开头但意图是文字

#这不是注释

3. 文本行含URL但不符合链接格式

Google https://google.com

4. 文本行为空(只有缩进)

 

3.6 空行维度

3.6.1 边界条件分析

场景示例潜在问题
无空行内容连续正常
单空行一行空分隔作用
多空行多个连续空行是否合并处理
文件开头空行\n: 编程是否忽略
文件结尾空行: 编程\n\n是否忽略

3.6.2 意外情况列表

1. 空行在缩进敏感位置

: 编程

 Google: https://google.com  # 空行后缩进是否重置?

2. 只有空行的文件


第四部分:组合测试(核心环节)

单个边界条件往往能被解析器处理,但多个边界条件的组合才是真正的挑战。

4.1 组合测试方法

组合测试用例 = 要素A的边界值 + 要素B的边界值 + 要素C的边界值

4.2 组合示例

组合1:空目录名 + 非法URL + 缩进错误

:
  Google: not a url

组合2:特殊字符 + 空注释 + 深层嵌套

: 编程&语言
  # 
  : 子目录
    Baidu: https://baidu.com

组合3:行末注释 + URL含空格 + 混合缩进

Google Search:
  https://google com # 注释
  : 另一个目录

组合4:空文件 + 只有注释 + 结尾空行

# 这是一个空文件

4.3 需要重点测试的组合类型

组合类型说明示例场景
空值组合多个要素同时为空空目录 + 空链接 + 空注释
特殊字符组合多个要素含特殊符号目录名含& + 链接名含: + URL含空格
结构破坏组合破坏层级结构缩进错误 + 空行干扰 + 非法行类型
性能压力组合极端值叠加超长名称 + 深层嵌套 + 大量空行

第五部分:转化为测试用例

每个意外情况都应该对应至少一个测试用例。

5.1 测试用例格式

test_case = {
    "name": "缩进不一致导致结构错误",
    "input": """
: 编程
  Google: https://google.com
    Baidu: https://baidu.com
""",
    "expected_behavior": "报告缩进错误,Baidu不应成为Google的子项",
    "error_location": "第3行,缩进4个空格"
}

5.2 测试分类

测试类型覆盖范围
单元测试单个要素的边界条件
集成测试要素之间的交互
压力测试极端值组合
模糊测试随机生成的非法输入

第六部分:推导清单模板

你可以用这个模板来系统化地推导任何语法的意外情况:

语法意外情况推导清单

1. 列出所有语法要素
   要素1: ________
   要素2: ________
   要素3: ________

2. 对每个要素,枚举边界值
   空值/缺失
   最小值
   最大值
   特殊字符
   非法格式

3. 对每个要素,枚举上下文干扰
   在注释中出现
   在字符串中出现
   在非法位置出现

4. 生成组合测试
   空值 × 空值
   特殊字符 × 特殊字符
   结构破坏 × 边界值

5. 转化为测试用例
   每个意外情况一个用例
   每个组合类型一个用例