Block Syntax Specification
@Doc Block Syntax Specification v1.4
0. Table of Contents
- 1. Design Philosophy
- 2. Document AST Structure
- 3. EBNF
- 4. Shared Components
- 5. Structural Blocks
- 6. Container Blocks
- 7. Callout Blocks
- 8. Widget Blocks
- 9. Metadata
- 10. Core Principle
- 11. Simplified Syntax Aliases
1. Design Philosophy
@Doc Block Syntax 採用:
Semantic First, Layout Later
區塊節點描述的是:
文件的語意(What)
而不是:
呈現方式(How)
因此 @Doc 不提供:
@div@span@flex@grid@row@col@class@style
Renderer 可以根據平台自由決定:
- HTML
- React
- DOCX
- Discord
- Terminal
- Notion
- AI UI
2. Document AST Structure
Document AST
│
├── Metadata
│ └── @meta
│
├── Block Nodes
│ │
│ ├── Structural Blocks
│ │ ├── @heading (alias: @h)
│ │ ├── @paragraph (alias: @p)
│ │ ├── @quote
│ │ ├── @list
│ │ ├── @code
│ │ ├── @img
│ │ ├── @table
│ │ ├── @hr
│ │ └── @svg
│ │
│ ├── Container Blocks
│ │ ├── @details
│ │ └── @card
│ │
│ ├── Callout Blocks
│ │ ├── @note
│ │ ├── @tip
│ │ ├── @important
│ │ ├── @warning
│ │ └── @caution
│ │
│ └── Widget Blocks
│ ├── @tabs
│ ├── @tab
│ └── @mermaid
│
└── Inline Nodes
│
├── Text Formatting
│ ├── @mark
│ ├── @color
│ ├── @bordered
│ ├── @bold (alias: @b)
│ ├── @italic (alias: @i)
│ ├── @underline (alias: @u)
│ ├── @del
│ └── @raw
│
├── Semantic Inline
│ ├── @sup
│ ├── @sub
│ ├── @kbd
│ └── @link
│
├── Footnotes
│ ├── @fn
│ └── @defn
│
└── Special Nodes
├── @n
└── @@
3. EBNF
document =
[ metadata ],
{ block-node } ;
block-node =
heading
| paragraph
| quote
| list
| code
| image
| table
| hr
| svg
| details
| card
| note
| tip
| important
| warning
| caution
| tabs
| mermaid ;
(* Note:
`tab` 不屬於 block-node。
它是 @tabs 專屬的子節點語法,只能出現在 tabs-content 內,
詳見下方 "Widget-Specific Grammar: @tabs"。
*)
metadata =
"@meta" , meta-content ;
(* meta-content is lexed the same way as block-content — the "[" / "]" pair
tokenizes normally, so an unregistered "@word" still falls back to plain
text per §6 Unknown Command Fallback — but Parser.ts is semantically
stricter here than for any other block node: it rejects every registered
node inside @meta, not just structural ones, not even @n or @raw. The
parser then splits the resulting text on newlines and the first "=" on
each line into key/value pairs and stores them directly on the AST node
(MetaNode.meta), rather than leaving that structuring to a later pass.
See Metadata.md §3/§6 for the full behavior and worked examples. *)
meta-content =
"[" ,
{ text } ,
"]" ;
heading =
( "@heading" | "@h" ) ,
[ "(" , level , ")" ] ,
block-content ;
paragraph =
( "@paragraph" | "@p" ) , block-content ;
quote =
"@quote" , block-content ;
list =
"@list" ,
[ "(" , "ordered" , ")" ] ,
block-content ;
code =
"@code" ,
[ language ] ,
raw-block-content ;
image =
"@img" ,
"(" ,
image-option-list ,
")" ,
[ styles ] ,
block-content ;
hr =
"@hr" ;
svg =
"@svg" ,
raw-block-content ;
details =
"@details" ,
[ title ] ,
[ styles ] ,
block-content ;
card =
"@card" ,
[ title ] ,
[ styles ] ,
block-content ;
note =
"@note" ,
[ title ] ,
[ styles ] ,
block-content ;
tip =
"@tip" ,
[ title ] ,
[ styles ] ,
block-content ;
important =
"@important" ,
[ title ] ,
[ styles ] ,
block-content ;
warning =
"@warning" ,
[ title ] ,
[ styles ] ,
block-content ;
caution =
"@caution" ,
[ title ] ,
[ styles ] ,
block-content ;
mermaid =
"@mermaid" ,
raw-block-content ;
(* ==========================================================================
Structural-Specific Grammar: @img
@img 的括號內容不是單一裸文字,而是以逗號分隔的
key=value 選項列表(image-option-list),可擴充。
第一個選項若省略 key,預設視為 src。
`image` 產生式(見上方)在 ")" 之後還有一個獨立的選填 [styles]——
Image Style v1,語法層與 image-option-list 完全無關(不能寫進括號
內),語意層見下方「Image Style v1」小節。
========================================================================== *)
image-option-list =
image-option ,
{ "," , image-option } ;
image-option =
src-option
| width-option
| height-option
| align-option
| radius-option
| border-option ;
src-option =
[ "src=" ] , url ;
width-option =
"width=" , integer ;
height-option =
"height=" , integer ;
align-option =
"align=" , ( "left" | "center" | "right" ) ;
radius-option =
"radius=" , text ;
border-option =
"border=" , text ;
url =
{ text-char - "," - ")" } ;
(* ==========================================================================
Widget-Specific Grammar: @table
@table 不使用通用的 block-content,
而是擁有專屬的結構化語法(Columns + Rows)。
========================================================================== *)
table =
"@table" , table-content ;
table-content =
"[" ,
cols ,
data ,
"]" ;
cols =
"@cols" ,
"[" ,
column-list ,
"]" ;
column-list =
cell ,
{ "," , cell } ;
data =
"@data" ,
"[" ,
{ row } ,
"]" ;
row =
"[" ,
cell ,
{ "," , cell } ,
"]" ;
(* A cell isn't plain text only — it also allows a curated subset of
inline-node (cell-inline-node), the same shape @cols columns and @data
cells share. The authoritative allowlist lives in registry.ts's
isCellAllowedNode(), not this grammar — a node outside that set (e.g.
@card, @table, @details) MUST throw rather than being silently dropped,
per Strict Mode (Inline Syntax Specification §11). *)
cell =
{ cell-inline-node | any-unicode-char - "," - "]" } ;
(* ==========================================================================
Widget-Specific Grammar: @tabs / @tab
@tab 僅能出現在 @tabs 的 tabs-content 內,
不屬於通用 block-node 集合,因此無法單獨出現在
document 頂層或其他 block-content 之中。
========================================================================== *)
tabs =
"@tabs" , tabs-content ;
tabs-content =
"[" ,
{ tab } ,
"]" ;
tab =
"@tab" ,
"(" ,
text ,
")" ,
block-content ;
4. Shared Components
block-content =
"[" ,
{ block-element } ,
"]" ;
block-element =
block-node
| inline-stream
| text ;
raw-block-content =
"[" ,
{ any-unicode-char } ,
"]" ;
title =
"(" ,
text ,
")" ;
language =
"(" ,
text ,
")" ;
level =
"1"
| "2"
| "3"
| "4"
| "5"
| "6" ;
text =
{ any-unicode-char } ;
integer、text-char等終結符定義沿用 Inline Spec 第 4 節 (完整 EBNF 語法定義)中的integer與text-char產生式, 兩份文件共用同一套字元集定義,此處不重複列出。
styles同樣沿用 Inline Spec 第 4 節的styles產生式("{" , { text-char - "}" } , "}"), 语法層仍只定義「花括號包裹的任意字元序列」;Container Blocks(@details、@card)、 Callout Blocks(@note、@tip、@important、@warning、@caution)與@img現在正式將其列入各自的產生式中(見上方第 5–7 節),而不再只是 Parser 端未經 EBNF 明文允許的附帶行為。Token 語意是各節點自己的規則,不是單一共用表。
@note/@tip/@important/@warning/@caution/@details沿用 Inline Spec 第 7 節@mark Styles Semantics的既有 color token 規則(具名 token 對照表 + hex 支援);@card與@img則各自是 獨立的封閉 token 集合——分別是Card Style v1(見下方第 6 節「Card Style v1」小節) 與Image Style v1(見下方第 5 節「Image Style v1」小節),共用同一組#RRGGBB/radius-Ntoken 形狀,但語意各自獨立(@card的 hex 是背景色,@img的 hex 是外框 色);兩者都不沿用 Inline Spec 第 7 節的具名色票。 Renderer 是否/如何把 Container/Callout/@img的styles映射成視覺樣式由各 Renderer 自行決定。
5. Structural Blocks
Heading
正典語法為 @heading;@h 是等效的簡化別名(Simplified Alias),兩者解析為同一個 AST 節點,Renderer 不會區分作者實際輸入的是哪一種寫法。
@heading(1)[
Introduction
]
@h(1)[
Introduction
]
HTML(兩種寫法輸出相同):
<h1>Introduction</h1>
Paragraph
正典語法為 @paragraph;@p 是等效的簡化別名。
@paragraph[
Hello World
]
@p[
Hello World
]
HTML(兩種寫法輸出相同):
<p>Hello World</p>
Quote
@quote[
Talk is cheap.
Show me the code.
]
HTML:
<blockquote>
Talk is cheap.
Show me the code.
</blockquote>
List
任何非空行都是一個項目;行首的 - 為選填的向下相容寫法,Parser 會自動去除:
@list[
Apple
Banana
Orange
]
等同於:
@list[
- Apple
- Banana
- Orange
]
每個項目在 AST 中是一個獨立的 list-item 節點(node.items),內容可包含行內節點(例如 @bold),不再只是純文字:
@list[
@bold[Apple] (今日特價)
Banana
]
AST:
List
└── items
├── ListItem [ Bold("Apple"), " (今日特價)" ]
└── ListItem [ "Banana" ]
有序清單
@list(ordered)[...] 會渲染成 <ol> 而不是預設的 <ul>。跟一般 @list 一樣,行首的 - 是選填、非必要——純文字行也算一個項目;額外寫 N. /N) 則是明確指定編號,Parser 會把這個數字存進該 ListItem 的 marker 欄位;Renderer 只在 ordered 為真時才會把 marker 轉成 <li value="N">,交給瀏覽器原生的 <ol> 計數器處理「跳號後自動接續」:
@list(ordered)[
- Apple
- Banana
3. Cherry
- Date
]
渲染結果為 1. Apple、2. Banana、3. Cherry(明確指定)、4. Date(自動接續)。
巢狀清單
沒有新增語法——@list 的內容本來就是 block-content,巢狀 @list[...] 已經是合法的子節點。單獨佔一行的巢狀 @list[...](前後只有空白)會被 Parser 併入前一個 item 的內容,而不是另開一個新 item:
@list[
- Fruits
@list[
- Apple
- Banana
]
- Vegetables
]
AST 上,內層 @list 節點會出現在 Fruits 這個 ListItem 的 content 陣列裡;Renderer 不需要任何額外邏輯,遞迴渲染 content 時自然會產生巢狀的 <ul>/<ol>。
舊版文件曾提過用
(modifier)陣列(例如@list(bullet,number)[...])逐層宣告清單型態的方案;上面這個@list(ordered)+ 巢狀子清單各自宣告型態的設計,是實際採用、比該提案更簡單的做法。
Code
@code(ts)[
const x = 1;
]
HTML:
<pre><code class="language-ts">
const x = 1;
</code></pre>
Image
@img 的括號內容為以逗號分隔的 key=value 選項列表(image-option-list),可擴充。第一個選項若省略 key=,預設視為 src:
@img(
https://example.com/logo.png
)[
WEDC Logo
]
等同於:
@img(src=https://example.com/logo.png)[
WEDC Logo
]
搭配其他選項使用:
@img(
https://example.com/logo.png,width=200,align=center
)[
WEDC Logo
]
目前支援的選項:
| 選項 | 說明 | 範例值 |
|---|---|---|
src(可省略) | 圖片來源 URL | src=https://... |
width | 顯示寬度(單位由 Renderer 決定) | width=200 |
height | 顯示高度(單位由 Renderer 決定) | height=150 |
align | 對齊方式 | align=left/center/right |
radius | 圓角(直接透傳給 Renderer 的 CSS 值) | radius=8px |
border | 外框(直接透傳給 Renderer 的 CSS 值) | border=1px solid #ccc |
Image Style v1
@img(...) 後面可以再接一個選填的 {styles}(語法定義見第 4 節),與 @card 共用同一套 Card Style v1 token 形狀(#RRGGBB / radius-N), 但語意獨立、自成一套封閉 token 集合——不沿用 Inline Spec 第 7 節的 具名色票,也不是 image-option-list 的一部分(不能寫在 (...) 括號 內):
| Token 形狀 | 語意 | 範例 |
|---|---|---|
#RRGGBB(16 進位 hex) | 外框色,套用為 1px 實線外框 | @img(src=...){#3366ff}[...] → border: 1px solid #3366ff |
radius-N(N 為非負整數) | 圓角,N 為像素值 | @img(src=...){radius-12}[...] → border-radius: 12px |
@img(src=https://example.com/photo.jpg){#3366ff,radius-12}[
WEDC Photo
]
省略 {styles} 時是純粹的裸 <img>,不會有任何預設圓角或外框——這跟 @card 通常有 Renderer 自己的靜態預設值可覆蓋不同,{styles} 對 @img 而言是純粹的「選填」開關,而非「覆蓋預設」。
Table
@table 內部結構固定為 @cols + @data 兩個專屬子節點,順序固定,兩者皆為必填:
@table[
@cols[id,name,price]
@data[
[1,早餐,60]
[2,午餐,80]
[3,晚餐,90]
]
]
@cols[...]:以逗號分隔的欄位標題列表,定義欄位順序與數量;每個欄位跟@data的儲存格一樣是cell(見下方),不限於純文字識別符。@data[...]:每一列包裝在[...]中,cell數量 SHOULD 與@cols定義的欄位數量一致;Parser MAY 對數量不符的列拋出警告或錯誤(由 Strict / Editor Mode 決定,參見 Inline Spec 第 11 節 Parser Recovery Strategy)。
每個 cell 不是單純的純文字——除了文字本身,還允許一組經過篩選的行內格式節點(@bold、@italic、@underline、@del、@mark、@color、@sup、@sub、@link、@fn,以及會被轉成換行的 @n),因為這些節點只改變文字的呈現方式,不會影響表格本身「欄位對齊資料列」的結構。這份清單由 Renderer 端維護(registry.ts 的 isCellAllowedNode),語法層本身不限制清單內容,未來可以擴充。不在清單上的節點(例如 @card、@table、@details 這類會帶來自己版面結構的區塊節點)MUST 拋出語法錯誤,而不是被靜默捨棄——這與 Strict Mode(Inline Syntax Specification 第 11 節)「寧可拋錯,也不要吞掉錯誤內容」的精神一致。
@table[
@cols[id,name,note]
@data[
[1,@bold[Alice],See @link(https://example.com)[profile]@n more info]
]
]
AST:
Table
├── Columns
│ ├── id
│ ├── name
│ └── price
└── Rows
├── Row [1, 早餐, 60]
├── Row [2, 午餐, 80]
└── Row [3, 晚餐, 90]
或是帶行內格式的儲存格:
Table
├── Columns
│ ├── id
│ ├── name
│ └── note
└── Rows
└── Row [
"1",
[ Bold("Alice") ],
[ "See ", Link("https://example.com", "profile"), "\n", " more info" ]
]
Horizontal Rule
@hr
HTML:
<hr>
SVG
@svg 與 @code/@mermaid 同屬 raw-block-content——內容原樣保留,Parser 不解析、不轉義:
@svg[
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
<circle cx="5" cy="5" r="4" />
</svg>
]
6. Container Blocks
@details/@card 現在也接受選填的 {styles}(語法定義見第 4 節),置於 (title) 之後、block-content 之前:
@card(API Key){#3366ff,radius-12}[
這裡放說明內容。
]
省略時維持純內容形式,兩者皆合法。Renderer MAY 忽略無法識別的 token (與 Inline Spec §6 Unknown Command Fallback 精神一致)。@card 的 {styles} token 語意是自己的一套封閉規則(Card Style v1),與 Inline Spec 第 7 節的具名 color token 對照表無關——見下方「Card Style v1」小節。
Details
@details(展開更多資訊)[
內容
]
HTML:
<details>
<summary>展開更多資訊</summary>
內容
</details>
Card
@card(API Key)[
這裡放說明內容。
]
Card Style v1
@card 的 {styles} 只允許少量、跨平台且高價值的樣式,刻意不做成通用 CSS 逃生口——目前只認以下兩種 token 形狀,可各自單獨出現、兩者併用(逗號分隔、 順序不拘),或整段省略:
| Token 形狀 | 語意 | 範例 |
|---|---|---|
#RRGGBB(16 進位 hex) | 背景色,直接採用該色值 | @card{#3366ff}[...] → background-color: #3366ff |
radius-N(N 為非負整數) | 圓角,N 為像素值 | @card{radius-12}[...] → border-radius: 12px |
@card{#3366ff,radius-12}[
同時設定背景色與圓角。
]
不在上表中的 token(例如具名色彩詞、Inline Spec 第 7 節的 color token)一律 視為無法識別,Renderer MUST 忽略而非拋錯(與 Inline Spec §6 Unknown Command Fallback 精神一致),並沿用 Renderer 自己的預設外觀。radius-N 的 N 若不是 純數字(例如 radius-lg)同樣視為無法識別。
7. Callout Blocks
@note、@tip、@important、@warning、@caution 皆可搭配選填的 (title)(定義見第 4 節),為內容附加一個獨立於本文的標題欄位;亦可搭配選填的 {styles}(見第 4 節、第 6 節 Container Blocks 的同一套說明),置於 (title) 之後;兩者皆省略時則維持純內容形式,皆合法:
Note
@note[
這是一般資訊。
]
Tip
@tip[
這是一個最佳實踐建議。
]
Important
@important[
請優先閱讀此內容。
]
Warning
@warning[
刪除後將無法復原。
]
搭配標題:
@warning(資料保留政策)[
刪除後將無法復原。
]
Caution
@caution[
此操作可能造成資料遺失。
]
8. Widget Blocks
Tabs
@tabs 內部僅能包含一個或多個 @tab 子節點,不接受其他 block-node 或裸文字:
@tabs[
@tab(JavaScript)[
...
]
@tab(Python)[
...
]
@tab(Rust)[
...
]
]
@tab(標題)[內容]:標題為分頁顯示名稱,內容為完整block-content(可包含任意 block-node 與 inline-stream)。- 若
@tabs[...]內出現非@tab的節點(例如裸文字或其他 block-node),Parser MUST 視為語法錯誤(Strict Mode)或由 Editor Mode 自動忽略 / 提示修正。
Mermaid
@mermaid[
graph TD
A --> B
]
9. Metadata
@meta[
title = @Doc
author = WEDC
description = AI Native Document Format
keywords = parser,ast,dsl
]
Renderer 可映射至:
- HTML Meta Tags
- OpenGraph
- PDF Metadata
- DOCX Properties
- Search Index
- RAG Metadata
10. Core Principle
@Doc Block Syntax 的目標並非建立新的 HTML。
而是建立:
Human Editable Machine Deterministic AI Friendly Cross Platform
的文件 AST。
HTML 是 Renderer。
Markdown 是 Renderer。
React 是 Renderer。
而 @Doc 是:
Source of Truth.
11. Simplified Syntax Aliases
部分高頻指令額外提供簡化別名(Simplified Alias)——純粹是輸入時的簡寫,Parser 會將其正規化為正典名稱後才建立 AST 節點(node.type 永遠是正典名稱),Renderer 完全不需要、也不會區分作者實際輸入的是哪一種寫法。
Block Syntax 涵蓋的別名:
| Canonical | Alias |
|---|---|
@heading | @h |
@paragraph | @p |
(Inline Syntax 的 @bold/@italic/@underline 別名 @b/@i/@u 定義在 Inline Syntax Specification。)
@Doc