| *English | 中文* |
CommonMark 0.31.2 是「Markdown 到底是什么意思」的 基准。本页按它的规范顺序走一遍每个构造,记录 GEML 对它的处理,最后列出 GEML 独有、 CommonMark 没有对应物的部分。
想看 Markdown / HTML / AsciiDoc / Org-mode 的横向能力矩阵,见 COMPARISON_CN.md。本页只对 CommonMark,但对得更深。
每一格「GEML」都是拿参考解析器实测出来的,不是照规范抄的——因为真正值得看的行, 恰恰是「同样的写法,出来的东西不一样」那些。GEML 不实现的构造不报错,它们退化成 普通文本,这正是需要知道它们的原因。
图例 —— ✅ 语法与语义相同 · ⚠️ 语法相同但结果不同 · 🔁 语法不同但能力对应 · ❌ 在 GEML 中不是构造(按普通文本解析)
| 主题 | CommonMark | GEML | |
|---|---|---|---|
| 编码 | 刻意不规定 —— “This spec does not specify an encoding” | 必须 UTF-8(§0.1) | 🔁 |
| 为什么 | 只用于渲染 | .gemlhistory 对文档字节做哈希,编码是承重结构 |
|
| BOM | 未提及 | 移除开头的一个 U+FEFF(§0.2) | 🔁 |
| 行结束符 | LF / CR / CRLF 都认 | 同样三种,解析前归一为 LF(§0.3) | ✅ |
| 空行 | 无字符,或只有空格/制表符 | 相同(§0.3) | ✅ |
U+0000 |
替换为 U+FFFD | 相同(§0.4) | ✅ |
| 制表符 | 复杂:4 列制表位、跨边界时部分展开(单独占一节) | 一个制表符算一列,用于列表缩进(§2.1) | ⚠️ |
| 反斜杠转义 | 任意 ASCII 标点 | 相同(§5.1) | ✅ |
| 实体引用 | & © # " 全部解码 |
不解码 —— 按字面文本 | ❌ |
| 非法输入 | 不存在这个概念:任何字节序列都是合法文档 | 文档可以带 error 诊断(附录 A);合规文档是没有 error 的那些(§8.1) |
🔁 |
最后一行是最深的差异,并且决定了其余一切:CommonMark 没有失败态,所以它那 655 个示例 是用来钉「每个输入是什么意思」;GEML 是构建期校验的——悬空引用是带稳定代码的 error——所以它的规范既要钉意思,也要钉诊断。
| CommonMark 构造 | 示例 | GEML | |
|---|---|---|---|
| ATX 标题 | # H1 … ###### H6 |
相同,并且自动派生 #id(## My Section → #my-section);显式 id 写作 ## Sec {#s} |
✅ |
| 闭合序列 | ## H2 ## |
尾部的 ## 当作文本保留(”H2 ##”) |
⚠️ |
| setext 标题 | Title / ===== |
不是构造——保持段落。排除它是为了让 = 只属于围栏(§1.6) |
❌ |
| 分隔线 | --- *** ___ |
不是构造——保持段落(§1.6) | ❌ |
| 缩进代码块 | 4 个空格 | 不是构造——保持段落。用 === code |
❌ |
| 围栏代码块 | ` js ` … ` ` |
变成内联代码 span,不是块 —— 见 §6 | ⚠️ |
| 同上(波浪号) | ~~~js … ~~~ |
保持段落 | ❌ |
| HTML 块 | <div>…</div> |
字面文本。按约束不存在原始 HTML 逃逸口(§1.5) | ❌ |
| 链接引用定义 | [foo]: /url "t" |
不是构造——保持段落。但脚注形式 [^f]: text 是支持的 |
❌ |
| 段落 | 任意文本 | 相同 | ✅ |
| 类型块 | — | === type {attrs} … === —— 代码/公式/表格/图形/提示框/元数据共用的唯一原语(§3) |
🔁 |
| CommonMark 构造 | 示例 | GEML | |
|---|---|---|---|
| 引用块 | > quoted |
不是构造——保持段落。提示框用 === note |
🔁 |
| 嵌套引用块 | > > deep |
同上——字面文本 | ❌ |
| 无序列表 | - * + |
只有 - 和 *;+ 不是标记 |
⚠️ |
| 有序列表 | 1. 1) |
只有 1.;1) 不是标记 |
⚠️ |
| 起始编号 | 5. 设定 start |
相同(§2.1) | ✅ |
| 紧凑 / 松散 | 项之间有空行则松散 | 相同(§2.1) | ✅ |
| 嵌套 | 按缩进,有内容列规则 | 按缩进,列计数,制表符 = 1 列 | ⚠️ |
| 多段落列表项 | 支持 | 不支持 —— 一项就是一行;富内容放进类型块(§2.1) | ❌ |
| 任务列表项 | GFM 扩展,不属于 CommonMark | 核心语法:- [ ] / - [x](§2.1) |
🔁 |
| CommonMark 构造 | 示例 | GEML | |
|---|---|---|---|
| 代码 span | `code` |
相同,反引号游程规则也相同 | ✅ |
| 强调 | *em* 和 _em_ |
只有 * —— _em_ 是字面文本(§5.3) |
⚠️ |
| 加粗 | **st** 和 __st__ |
只有 ** —— __st__ 是字面文本 |
⚠️ |
| 定界符算法 | flanking 游程 + 三的规则 | 完全相同的算法,只是限定在 * 和 ~~(§5.3) |
✅ |
| 删除线 | GFM 扩展,不属于 CommonMark | 核心语法:~~s~~(单个 ~ 是字面) |
🔁 |
| 内联链接 | [t](/url) |
语法相同。但不带 scheme 的目标会被当成文档引用而非 href —— 见 §6 | ⚠️ |
| 链接标题 | [t](/url "ti") |
那不是链接标题;选项写进属性对象:[t](url){rel=nofollow} |
⚠️ |
| 引用式链接 | [t][ref] + [ref]: /url |
不是构造——字面文本 | ❌ |
| 图片 |  |
相同,并推广到音频/视频(按扩展名或 as=,§5.1) |
✅ |
| 自动链接 | <https://e.com>、<a@b.com> |
不是构造——字面文本。写 [text](url) |
❌ |
| 内联原始 HTML | a <b>bold</b> c |
字面文本(§1.5) | ❌ |
| 硬换行 | 行尾两个空格,或 \ |
只有 \ —— 行尾空格不起作用 |
⚠️ |
| 软换行 | 段落内的换行 | 相同 | ✅ |
| 内联公式 | — | $E=mc^2$,正文逐字(§5.1) |
🔁 |
CommonMark 没有对应物——这些正是这个格式存在的理由。
| 能力 | 语法 | 说明 |
|---|---|---|
| 类型块 | === code {lang=js} … === |
代码/公式/表格/图形/提示框/元数据共用一个原语。围栏是 ≥3 个 =;更长的围栏可嵌套;正文自身含围栏时用 === #id 按标签闭合(§3) |
| 块身份 | {#budget} |
文档内唯一——块的主键(§4) |
| 类 / 参数 | {.warning caption="Annual cost"} |
语义类与类型自定义参数(§4) |
| 可校验引用 | [t](#budget)、[[#budget]]、other.geml#budget |
构建期解析;悬空引用是 error,而不是事后才发现的坏链接(§5.2) |
| 自动引用 | [[#budget]] |
链接文字取自目标的 caption / 标题 |
| 脚注 | [^f] + [^f]: text |
核心语法,不是扩展 |
| 文档元数据 | === meta,每行 key = value |
取代 frontmatter |
| 插值 | `` | 从 meta 取值;未知 key 是 error;代码 span 与公式内不替换;\ 转义 |
| 计算表格 | compute="FY = Q1+Q2+Q3+Q4"、summary="…sum(FY)…" |
构建期算出计算列与汇总行;公式按求值顺序天然无环(§6) |
| 外部表数据 | src="data.csv" format=csv |
渲染时抓取 |
| 合并单元格 | span="r2c1:2x1" |
声明式,不是画出来的 |
| 托管图形 | === diagram {format=mermaid} |
正文原样交给外部渲染器;GEML 自身不定义图形语言(§7) |
| 绑定数据的图表 | === diagram {format=geml-chart data=#fy25 type=bar x=Segment y=FY} |
图表完全由属性描述,所以列名能在构建期对着表校验(§7.1) |
| 可寻址散文 | === text {#intro} |
给一段散文一个 id,使其可被引用、可块级编辑 |
| 捕获的输出 | === output {of=#code-id} |
代码块的结果,由工具记录,永不执行 |
| 隐藏内容 | {hidden}、%% 行 |
在模型中且受引用校验,但不渲染 |
| 诊断 | — | 稳定代码 + 固定严重级别(附录 A) |
| 版本 | .gemlhistory 边车 |
块级撤销(geml revert file #id) |
以下是「同样的按键、出来不同文档」的几行,四条都是实测的,不是推断的。
1. ` ``` ` 围栏是代码 span,不是代码块。
```js
x = 1
```
解析结果是一个段落,里面一个内联代码 span,值为 "js\nx = 1\n" —— 反引号游程开启了
代码 span,换行落在它内部,闭合游程结束它。不报任何错,渲染出来也大致像那么回事,
这正是它成为陷阱的原因。要用类型块:
=== code {lang=js}
x = 1
===
2. 不带 scheme 的链接目标是文档引用。
[t](/url) 产生的链接其 doc 是 /url —— GEML 会试图把它当跨文档引用解析,并
告警 unchecked-cross-document-reference。[t](https://example.com) 才是普通 href。
而且只有 http、https、mailto、tel 会被输出为可导航目标(§9.5)。
3. _下划线_ 和 __双下划线__ 是字面文本。
GEML 跑的是 CommonMark 一模一样的定界符游程算法,但只作用于 * 和 ~~。这是故意的:
标识符里的 _(my_var_name)是 Markdown 最常见的误强调 bug,而去掉它没有任何代价——
* 覆盖同样的场景。
4. 行尾两个空格不是换行。
只有行尾的 \ 是。一个会被去尾空格工具静默删掉的不可见语法,不该出现在一个以 diff 和
机器编辑为目标的格式里。
geml notes.md 把 Markdown 转成 GEML:
| Markdown | 变成 |
|---|---|
| YAML frontmatter | === meta |
| ` ```lang ` 围栏代码 | === code {lang=…} |
` ```mermaid ` / graphviz / … |
=== diagram {format=…} |
$$ … $$ |
=== math |
> 引用块 |
=== note |
| GFM 管道表格 | === table |
[^f]: text 脚注定义 |
=== note {#f} |
<https://e.com> 自动链接 |
[https://e.com](https://e.com) |
| setext 标题 | ATX 标题 |
| 分隔线 | 丢弃(会作为 note 报出) |
转换出的块会自动获得 id(#note-1、#code-1、#table-1 …),所以结果立刻就是
可寻址、可块级编辑的。内联语法原样通过,因为 GEML 的内联文法是 Markdown 作者实际会用
的那个子集的超集。
geml <file.geml> --to md 是反向转换,天然有损——Markdown 没有类型块原语——所以每个
无法映射的构造(geml-chart、{hidden}、块 id)都会被报出,而不是静默丢弃。
CommonMark 的任务是说清「人们已经在写的 Markdown」是什么意思,且没有失败态。它的约束是 兼容十年积累的既有文档,所以它必须带着 setext 标题、四种强调写法、两族列表标记,以及 一个 HTML 逃逸口。
GEML 没有这些包袱。它的约束(§1)是:文件无需渲染即可当纯文本读、所有结构化内容共用
一个原语、引用在构建期校验、语义不绑定任何渲染后端。上面每一处「删减」都在买其中
一条:去掉 setext 与分隔线,= 和 --- 就腾给围栏且不再歧义;去掉原始 HTML,语义就与
后端无关;去掉 _ 强调和行尾空格换行,就消掉两类静默误解析。
结果是一个更小的语法面,对接受什么更严格——而且第二个实现能照着规范复现出来,这正是 GEML 给自己定的验收标准(§8.4)。