GEML 块类型图解 · 第 1 页 / 5 · 规范 1.0

GEML 块图解 · 简单四型

这一页是 meta、math、note、text 四个体量最小的类型,外加所有类型共用的围栏、id、属性规则。每条规则标出处:规范的章节号,或某个 GEP。右边不是想象的效果,是处理器实际怎么对待它:geml check 的诊断、geml list 给的地址、--to html 吐出的标签。发现的两处实现与规范不一致也标在看板里。

看板

19 条规则,各自的出处和状态

规范已定 规范正文写死的。GEP 草案 提案定义、本分支已实现但规范未收。实现偏差 规范这样说,1.9.2 不这样做。

规范已定 18 GEP 草案 1
规则出处状态
所有类型共用
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规范已定
6caption 和 hidden 对所有类型有效。[[#id]] 的链接文字取目标的 caption 或标题,没有就用 id。hidden 块进模型、受检、不渲染。§4 · §5.2规范已定
7%% 只在块位置(顶层或 flow 体内)是注释;raw 体内是正文原样保留。§4规范已定
8两块之间的散文有派生地址:P-between-N、C-before-N、C-after-P;只匹配不反解。§4规范已定
meta
9key–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规范已定
13profile 是保留键,声明应用层词汇;title 放 meta 不放 H1(风格建议)。§4 · §8.6规范已定
14#meta["version"] 按坐标读一个合并后的值。GEP-0011GEP 草案
math
15raw 体的块级公式,渲染为 \[…\];行内用 $…$。正文交给数学渲染器,处理器不解释。§3 · §5.1规范已定
note
16flow 体,可嵌块、带行内标记。渲染为 <aside class="callout note …">,.class 进 class 名。它是有 chrome 的提示框,不是中性容器。§3 · §4规范已定
17[^id] 脚注可以指向任何带 id 的块,通常是 note。§5.2规范已定
text
18flow 体的中性容器,只为给一段散文加 id 和属性。渲染为 <div class="text">,无 chrome;只包真需要寻址的散文。§3 · GEP-0004规范已定
19![[#id]] 只能投影单段 text;多段或非 text 是 inline-transclusion-not-inline error,且只报这一条。§5.2 · A.2规范已定
共同

所有类型共用的规则

围栏怎么开合、未知的东西怎么降级、哪些属性谁都能用。先看这些,六个类型各自的部分就短了。

围栏:等长裸围栏和标签围栏,谁先到谁关 规范已定标签围栏 === #id 免去数 =,但体内一个等长裸围栏照样把块提前关掉。处理器会指出来。
GEML · p1-fences.geml
==== 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              ← 关的是已经关掉的块
geml check · geml list
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   ← 掉出来的两行成了散文
规则
§3:块由先出现的关闭者关闭,id 不禁用等长裸关。推荐外围栏比体内任何围栏状行都长(==== 包 ===);标签围栏推荐给长块用,防数错,不防截断。
地址
掉出块的两行按 §4 得到派生地址 #fences-after-early:容器 #fences 里、#early 之后、没有下一个块。这就是「散文也有地址」。
没写大括号、没关块 规范已定两种最常见的手误,一种是 warning,一种是 error。
GEML · p1b-fencelike.geml
=== embed src=#f          ← 属性没加 {},整行是段落
This line follows a fence-like line that had no braces.

=== note {#open}
This block is never closed.  %% 文件到此为止
geml check
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
为什么
A.1:fence-like-line 是 warning,因为那一行确实按段落解析了,文档仍然成立,只是里面的引用不会被检查。unterminated-block 是 error,因为正文一直吃到文件尾,作者的意图已经丢了。
未知类型、未知属性、hidden、%% 规范已定四种「处理器不认识或不该显示」的东西,各有各的降级方式。
GEML · p4-blocks.geml 节选
%% 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
===
--to html 的对应输出
#hiddennote:没有输出。块在模型里,[[#hiddennote]] 解析成功,页面上什么都没有。
aside.callout.note

Unknown attribute on a known type.

warning · unknown attribute `foo` for block type `note`
figure › pre.diagram-src data-type="fancy"
An unknown type keeps its body raw: **not parsed**.
warning · unknown block type `fancy`; body kept as raw

See hiddennote.

tsexport const x = 1; %% this percent line is body text inside a raw block
降级
§8.2(6):不认识的类型正文原样保留,永不丢内容;**not parsed** 里的星号就是字面。未知属性同理,保留并 warning。
hidden vs %%
§4 的分工:hidden 给要进文档模型但不显示的结构化内容(喂图表的数据源、可复用片段);%% 给不进模型的随手注释。raw 体内的 %% 是正文,上面 ts 代码块里那一行原样进了 <code>。
meta

meta:键值体,合并成一个命名空间

它不渲染。它提供三样东西:文档标题、{{key}} 插值的值、profile 声明。

两个 meta 合并,先定义者胜;{{key}} 单遍插值 规范已定
GEML · p2-meta.geml
=== 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.
===
geml check · geml get
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
Meta probetitle → 页面标题

Version 3, owner docs, escaped {{version}}, code {{version}}.

caption="{{title}}"

Attribute values are not interpolated.

值类型
§4:引号字符串、true/false、匹配数字语法的裸词是数字,其余裸词是字符串。version = 3 是数字,draft = true 是布尔。没有数组、日期、嵌套表。
插值
单遍:替换进来的值不再扫描,所以 a = "{{b}}" 与 b = "{{a}}" 不会循环。三个不插的地方:code span 和行内数学内、属性值内、raw 体内。\{{version}} 得到字面的六个字符。
建议
标题放 title = 而不是 H1,这样每个标题都是文档真正的一节(§4 风格说明)。
#meta 是唯一保留 id:两个 meta 时不许别的块叫它 规范已定规范说 error,check 也报了。
GEML · p2b-reserved.geml
=== meta
title = "a"
===
=== meta
owner = "b"
===
=== note {#meta}          ← §4:两个以上 meta 时应为 reserved-id error
Declared #meta with two meta blocks.
===
geml check · geml list
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
规范
§4 与 A.2:#meta 命名的是合并结果,不是某一个块。只有一个 meta 时块和合并是同一件事,允许它带 {#meta};两个以上时同一地址两种读法,是 error。
为什么要紧
GEP-0011 把 #meta 作为坐标根(#meta["title"]),同一个地址有两种读法,就会答出两个不同的值。
math

math:块级公式

最简单的类型。raw 体,交给数学渲染器;处理器只负责不碰它。

块级与行内 规范已定
GEML · p4-blocks.geml 节选
=== 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]].
--to html
eiπ + 1 = 0

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>
规则
§3 注册为 raw;§5.1 行内是 $…$,同样 verbatim。{{key}} 在行内数学里不插值(§5.3 阶段 1)。[[#euler]] 的文字取 caption「Euler」。
没有的
没有 format=:数学没有 diagram 那样的多种 DSL 之争,正文就是 TeX 风格文本,渲染器自选实现。
note

note:带 chrome 的提示框

flow 体,可以嵌块。它和 text 的区别就是有没有 chrome。

callout,嵌一个代码块,.warning 进 class 规范已定
GEML · p4-blocks.geml 节选
==== note {#warn .warning caption="Careful"}
A callout with **inline** markup and a nested block:
=== code {lang=sh}
geml check doc.geml
===
==== #warn
Reference: [[#warn]].
--to html
aside.callout.note.warning #warn

A callout with inline markup and a nested block:

shgeml check doc.geml

Reference: Careful.

规则
§3 注册为 flow:正文按行内和块解析,所以 **inline** 是真粗体,嵌的 code 是真块。§4 的 .class 只是语义类,规范不规定样式;渲染器把它接到 class 上。
与 text
要一段散文可寻址但不要提示框的外观,用 text。要脚注,[^id] 指向一个 note 是常见写法(§5.2)。
围栏
体内有 ===,外围栏必须更长:====。这里同时用了标签关闭 ==== #warn,两者不冲突。
text

text:只为给散文一个 id

GEP-0004 加进来的。中性容器,无 chrome;它存在的理由是 geml get/set #intro、[[#intro]] 和版本回滚。

可寻址散文,以及 ![[#id]] 只投影单段 规范已定单段能投影;多段报错,且只报这一条。
GEML · p4-blocks.geml 节选
=== text {#intro}
One paragraph of addressable prose.
===
=== text {#two}
First paragraph.

Second paragraph.
===
Projection of one paragraph: ![[#intro]]. Projection of two: ![[#two]].
Reference: [[#intro]].
geml check · --to html
error: `![[#two]]` projects inline content, but the target is not a
  single-paragraph `text` block; for block content use
  `=== embed {src=#two}` (line 28)
div.text #intro

One paragraph of addressable prose.

div.text #two

First paragraph.

Second paragraph.

Projection of one paragraph: One paragraph of addressable prose. Reference: intro.

规则
§5.2:![[#id]] 是行内投影,把目标的行内内容插到句子里,所以目标必须是恰好一段的 text 块。多段、标题、别的类型都是 inline-transclusion-not-inline error;要整块投影用 === embed {src=#two}。
无 caption
[[#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.gemlmeta 合并、插值、转义、属性不插值、未知键1 error 1 warningmeta · 图 1
p2b-reserved.geml · p2c-single.geml两个 meta 下的 {#meta};单个 meta 带 {#meta}1 error 前者报 reserved-id,后者干净meta · 图 2,看板 11
p4-blocks.gemlmath、note 嵌块、text 投影、hidden、未知属性、未知类型、%%、caption 自动文字1 error 2 warning共同 · 图 3,math,note,text,看板 19