这一页是 meta、math、note、text 四个体量最小的类型,外加所有类型共用的围栏、id、属性规则。每条规则标出处:规范的章节号,或某个 GEP。右边不是想象的效果,是处理器实际怎么对待它:geml check 的诊断、geml list 给的地址、--to html 吐出的标签。发现的两处实现与规范不一致也标在看板里。
规范已定 规范正文写死的。GEP 草案 提案定义、本分支已实现但规范未收。实现偏差 规范这样说,1.9.2 不这样做。
| 规则 | 出处 | 状态 | |
|---|---|---|---|
| 所有类型共用 | |||
| 1 | 围栏是 ≥3 个 =。块由先出现的关闭者关闭:等长的裸围栏,或(有 id 时)标签围栏 === #id。 | §3 | 规范已定 |
| 2 | 嵌套只靠围栏长度。标签围栏免去数 =,但不防体内等长裸围栏提前关块;发生时给 stray-labeled-fence warning。 | §3 · A.1 | 规范已定 |
| 3 | 没写大括号的属性让整行退化为段落,给 fence-like-line warning;块没关到文件尾是 unterminated-block error。 | §3.1 · A.1 | 规范已定 |
| 4 | 未注册类型是 warning,正文按 raw 保留;渲染成带类型标签的 <figure><pre>。 | §3 · §8.2(6) | 规范已定 |
| 5 | 已知类型上的未知属性键是 unknown-attribute warning,保留不丢。 | §4 | 规范已定 |
| 6 | caption 和 hidden 对所有类型有效。[[#id]] 的链接文字取目标的 caption 或标题,没有就用 id。hidden 块进模型、受检、不渲染。 | §4 · §5.2 | 规范已定 |
| 7 | %% 只在块位置(顶层或 flow 体内)是注释;raw 体内是正文原样保留。 | §4 | 规范已定 |
| 8 | 两块之间的散文有派生地址:P-between-N、C-before-N、C-after-P;只匹配不反解。 | §4 | 规范已定 |
| meta | |||
| 9 | key–value 体,一行一对。值类型:引号字符串、true/false、数字、其余裸词是字符串。没有数组、日期、嵌套。 | §3 · §4 | 规范已定 |
| 10 | 多个 meta 合并;同键先定义者胜,后来的是 duplicate-meta-key warning。#meta 命名合并结果,是唯一保留 id。 | §4 | 规范已定 |
| 11 | 文档有两个以上 meta 时,别的块声明 {#meta} 是 reserved-id error。 | §4 · A.2 | 规范已定 |
| 12 | {{key}} 单遍插值,未知键 error;code span 和行内数学里不插;属性值里不插;\{{key}} 得字面。 | §4 · §5.3 | 规范已定 |
| 13 | profile 是保留键,声明应用层词汇;title 放 meta 不放 H1(风格建议)。 | §4 · §8.6 | 规范已定 |
| 14 | #meta["version"] 按坐标读一个合并后的值。 | GEP-0011 | GEP 草案 |
| math | |||
| 15 | raw 体的块级公式,渲染为 \[…\];行内用 $…$。正文交给数学渲染器,处理器不解释。 | §3 · §5.1 | 规范已定 |
| note | |||
| 16 | flow 体,可嵌块、带行内标记。渲染为 <aside class="callout note …">,.class 进 class 名。它是有 chrome 的提示框,不是中性容器。 | §3 · §4 | 规范已定 |
| 17 | [^id] 脚注可以指向任何带 id 的块,通常是 note。 | §5.2 | 规范已定 |
| text | |||
| 18 | flow 体的中性容器,只为给一段散文加 id 和属性。渲染为 <div class="text">,无 chrome;只包真需要寻址的散文。 | §3 · GEP-0004 | 规范已定 |
| 19 | ![[#id]] 只能投影单段 text;多段或非 text 是 inline-transclusion-not-inline error,且只报这一条。 | §5.2 · A.2 | 规范已定 |
围栏怎么开合、未知的东西怎么降级、哪些属性谁都能用。先看这些,六个类型各自的部分就短了。
=== #id 免去数 =,但体内一个等长裸围栏照样把块提前关掉。处理器会指出来。==== note {#outer} Outer holds an inner block. === code {lang=sh} echo hi === ==== %% 外长内短,嵌套安全 === note {#labeled} A long block closed by a labeled fence. === #labeled %% 标签围栏:不用数长度 === note {#early} This body contains a bare run of the opening length: === ← 等长裸围栏,块在这里就关了 which closed the block above at that line. === #early ← 关的是已经关掉的块
warning: labeled fence for `#early` at line 22, but block `#early` was already closed by a bare fence at line 20 — body may be silently truncated (line 22) 0 error(s), 1 warning(s) $ geml list p1-fences.geml #outer note L7-12 === code code anon L9-11 #labeled note L14-16 #early note L18-20 ← 只到 L20 #fences-after-early prose L21-22 ← 掉出来的两行成了散文
==== 包 ===);标签围栏推荐给长块用,防数错,不防截断。#fences-after-early:容器 #fences 里、#early 之后、没有下一个块。这就是「散文也有地址」。=== embed src=#f ← 属性没加 {},整行是段落 This line follows a fence-like line that had no braces. === note {#open} This block is never closed. %% 文件到此为止
warning: line looks like an open fence for `embed` but is not one — attributes must be braced (`=== embed {…}`); the line reads as plain paragraph text (line 5) error: unterminated `note` block (no matching === or `=== #open`) (line 8) 1 error(s), 1 warning(s) exit 1
fence-like-line 是 warning,因为那一行确实按段落解析了,文档仍然成立,只是里面的引用不会被检查。unterminated-block 是 error,因为正文一直吃到文件尾,作者的意图已经丢了。%% 规范已定四种「处理器不认识或不该显示」的东西,各有各的降级方式。%% a top-level comment: kept in the file, never rendered === note {#hiddennote hidden} Not rendered, but referenced: still checked. === === note {#badattr foo=bar} Unknown attribute on a known type. === === fancy {#unknown} An unknown type keeps its body raw: **not parsed**. === See [[#hiddennote]]. === code {lang=ts} export const x = 1; %% this percent line is body text inside a raw block ===
[[#hiddennote]] 解析成功,页面上什么都没有。Unknown attribute on a known type.
warning · unknown attribute `foo` for block type `note`See hiddennote.
**not parsed** 里的星号就是字面。未知属性同理,保留并 warning。hidden 给要进文档模型但不显示的结构化内容(喂图表的数据源、可复用片段);%% 给不进模型的随手注释。raw 体内的 %% 是正文,上面 ts 代码块里那一行原样进了 <code>。meta:键值体,合并成一个命名空间它不渲染。它提供三样东西:文档标题、{{key}} 插值的值、profile 声明。
{{key}} 单遍插值 规范已定=== meta title = "Meta probe" version = 3 draft = true === === meta title = "Second title" ← 同键,后到者被忽略 owner = "docs" === # Interpolation {#interp} Version {{version}}, owner {{owner}}, escaped \{{version}}, code `{{version}}`. Unknown {{nope}} key. === note {#cap caption="{{title}}"} Attribute values are not interpolated. ===
warning: meta key `title` already defined at line 1; later definition at line 6 is ignored (line 6) error: unknown metadata reference `{{nope}}` (line 15) $ geml get p2-meta.geml '#meta' ← 合并后的命名空间 title = "Meta probe" version = 3 draft = true owner = "docs" $ geml get p2-meta.geml '#meta["version"]' GEP-0011 3
Version 3, owner docs, escaped {{version}}, code {{version}}.
Attribute values are not interpolated.
true/false、匹配数字语法的裸词是数字,其余裸词是字符串。version = 3 是数字,draft = true 是布尔。没有数组、日期、嵌套表。a = "{{b}}" 与 b = "{{a}}" 不会循环。三个不插的地方:code span 和行内数学内、属性值内、raw 体内。\{{version}} 得到字面的六个字符。title = 而不是 H1,这样每个标题都是文档真正的一节(§4 风格说明)。#meta 是唯一保留 id:两个 meta 时不许别的块叫它 规范已定规范说 error,check 也报了。=== meta title = "a" === === meta owner = "b" === === note {#meta} ← §4:两个以上 meta 时应为 reserved-id error Declared #meta with two meta blocks. ===
error: `#meta` is reserved for this document's merged meta namespace, and this document has more than one `meta` block — give the block another id (line 7) $ geml list p2b-reserved.geml === meta@61f06c79 meta anon L1-3 === meta@0e8db6c1 meta anon L4-6 #meta note L7-9 ← 同一个地址,一个读者读到 note,处理器读到合并 $ geml check p2c-single.geml 单个 meta 自带 {#meta}:合法 ok: no diagnostics
#meta 命名的是合并结果,不是某一个块。只有一个 meta 时块和合并是同一件事,允许它带 {#meta};两个以上时同一地址两种读法,是 error。#meta 作为坐标根(#meta["title"]),同一个地址有两种读法,就会答出两个不同的值。math:块级公式最简单的类型。raw 体,交给数学渲染器;处理器只负责不碰它。
=== math {#euler caption="Euler"} e^{i\pi} + 1 = 0 === Inline math $a^2+b^2=c^2$ stays inline; block math is the typed block. Reference: [[#euler]].
Inline math a²+b²=c² stays inline; block math is the typed block. Reference: Euler.
<div class="math-block" id="euler">\[e^{i\pi} + 1 = 0\]</div>
$…$,同样 verbatim。{{key}} 在行内数学里不插值(§5.3 阶段 1)。[[#euler]] 的文字取 caption「Euler」。format=:数学没有 diagram 那样的多种 DSL 之争,正文就是 TeX 风格文本,渲染器自选实现。note:带 chrome 的提示框flow 体,可以嵌块。它和 text 的区别就是有没有 chrome。
.warning 进 class 规范已定==== note {#warn .warning caption="Careful"} A callout with **inline** markup and a nested block: === code {lang=sh} geml check doc.geml === ==== #warn Reference: [[#warn]].
A callout with inline markup and a nested block:
Reference: Careful.
**inline** 是真粗体,嵌的 code 是真块。§4 的 .class 只是语义类,规范不规定样式;渲染器把它接到 class 上。text。要脚注,[^id] 指向一个 note 是常见写法(§5.2)。===,外围栏必须更长:====。这里同时用了标签关闭 ==== #warn,两者不冲突。text:只为给散文一个 idGEP-0004 加进来的。中性容器,无 chrome;它存在的理由是 geml get/set #intro、[[#intro]] 和版本回滚。
![[#id]] 只投影单段 规范已定单段能投影;多段报错,且只报这一条。=== text {#intro} One paragraph of addressable prose. === === text {#two} First paragraph. Second paragraph. === Projection of one paragraph: ![[#intro]]. Projection of two: ![[#two]]. Reference: [[#intro]].
error: `![[#two]]` projects inline content, but the target is not a
single-paragraph `text` block; for block content use
`=== embed {src=#two}` (line 28)
One paragraph of addressable prose.
First paragraph.
Second paragraph.
Projection of one paragraph: One paragraph of addressable prose. Reference: intro.
![[#id]] 是行内投影,把目标的行内内容插到句子里,所以目标必须是恰好一段的 text 块。多段、标题、别的类型都是 inline-transclusion-not-inline error;要整块投影用 === embed {src=#two}。[[#intro]] 的文字回落成 id 本身「intro」,因为 text 块没有 caption 也没有标题。想要好看的链接文字,给它 caption=。全部用仓库内当前构建 node geml-parser/dist/geml.js 跑出。探针文件在会话临时目录,内容已完整贴在上面各图左侧。
| 探针 | 覆盖 | check 结果 | 页面里对应 |
|---|---|---|---|
| p1-fences.geml | 嵌套围栏、标签围栏、等长裸围栏提前关 | 1 warning stray-labeled-fence | 共同 · 图 1 |
| p1b-fencelike.geml | 无括号属性、未关块 | 1 error 1 warning | 共同 · 图 2 |
| p2-meta.geml | meta 合并、插值、转义、属性不插值、未知键 | 1 error 1 warning | meta · 图 1 |
| p2b-reserved.geml · p2c-single.geml | 两个 meta 下的 {#meta};单个 meta 带 {#meta} | 1 error 前者报 reserved-id,后者干净 | meta · 图 2,看板 11 |
| p4-blocks.geml | math、note 嵌块、text 投影、hidden、未知属性、未知类型、%%、caption 自动文字 | 1 error 2 warning | 共同 · 图 3,math,note,text,看板 19 |