@Doc

Block Syntax Specification

@Doc Block Syntax Specification v1.4

0. Table of Contents


1. Design Philosophy

@Doc Block Syntax 採用:

Semantic First, Layout Later

區塊節點描述的是:

文件的語意(What)

而不是:

呈現方式(How)

因此 @Doc 不提供:

  • @div
  • @span
  • @flex
  • @grid
  • @row
  • @col
  • @class
  • @style

Renderer 可以根據平台自由決定:

  • HTML
  • React
  • PDF
  • 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 } ;

integertext-char 等終結符定義沿用 Inline Spec 第 4 節 (完整 EBNF 語法定義)中的 integertext-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-N token 形狀,但語意各自獨立(@card 的 hex 是背景色,@img 的 hex 是外框 色);兩者都不沿用 Inline Spec 第 7 節的具名色票。 Renderer 是否/如何把 Container/Callout/@imgstyles 映射成視覺樣式由各 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 會把這個數字存進該 ListItemmarker 欄位;Renderer 只在 ordered 為真時才會把 marker 轉成 <li value="N">,交給瀏覽器原生的 <ol> 計數器處理「跳號後自動接續」:

@list(ordered)[
- Apple
- Banana
3. Cherry
- Date
]

渲染結果為 1. Apple2. Banana3. Cherry(明確指定)、4. Date(自動接續)。

巢狀清單

沒有新增語法——@list 的內容本來就是 block-content,巢狀 @list[...] 已經是合法的子節點。單獨佔一行的巢狀 @list[...](前後只有空白)會被 Parser 併入前一個 item 的內容,而不是另開一個新 item:

@list[
- Fruits
  @list[
  - Apple
  - Banana
  ]
- Vegetables
]

AST 上,內層 @list 節點會出現在 Fruits 這個 ListItemcontent 陣列裡;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(可省略)圖片來源 URLsrc=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.tsisCellAllowedNode),語法層本身不限制清單內容,未來可以擴充。不在清單上的節點(例如 @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-NN 若不是 純數字(例如 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 涵蓋的別名:

CanonicalAlias
@heading@h
@paragraph@p

(Inline Syntax 的 @bold/@italic/@underline 別名 @b/@i/@u 定義在 Inline Syntax Specification。)