GEML vs. other markup formats

September 4, 2026 · View on GitHub

English | 中文

How GEML compares to Markdown (GitHub-flavored), HTML, CommonMark (strict core), AsciiDoc, Org-mode, and Pandoc Markdown.

Note on Pandoc: it is really a converter plus its own Pandoc Markdown — the most feature-complete Markdown dialect. Its attribute syntax {#id .class key=val} is in fact the ancestor of GEML's. Pandoc's own super-powers — multi-format conversion and programmable Lua filters — sit on a different axis than the per-element comparison below.

A note on framing: this is not a checkbox race. AsciiDoc, in particular, ships more built-in elements out of the box than GEML does. GEML's case rests on three things no other format here offers together — and the comparison is meant to make those visible, not to win a feature count:

  1. One primitive for every structured block — lowest syntax surface to learn, parse, or generate (which is why it's friendly to AI).
  2. Build-time reference checking — a broken cross-reference is an error, not a silent dead link — and it covers content, not just links: a transcluded block, an external CSV, a .jsonl log, a code line range.
  3. Self-contained version history (.gemlhistory) — without git or any online service.

Legend: ✓ native · ◐ via extension/convention · ✗ none · (H) needs raw HTML.

For a construct-by-construct walk through CommonMark 0.31.2 alone — every leaf block, container block and inline, with what GEML does to each — see GEML vs. CommonMark.

Capability matrix

Element / capabilityGEMLMarkdown (GFM)HTMLCommonMarkAsciiDocOrg-modePandoc Markdown
Headings
Bold / italic
Inline code
Code block (with language)
Lists
Links / images
Audio / video embed(H)(H)
Tables
Data / computed-column tables◐ csv◐ formulas
Structured data as a verified block✓ json/jsonl◐ unchecked◐ unchecked◐ babel
Body pulled from an external file✓ checked✓ unchecked✓ unchecked◐ filter
Admonitions / callouts◐ alerts◐ fenced div
Footnotes
Definition lists
Super/subscript, inline spans
Math (inline / block)
Diagrams (hosted DSL)◐ mermaid◐ filter
Chart bound to a table or data block
Citations / bibliography
Document metadata✓ native block◐ frontmatter
Block id + cross-reference◐ headings only
Transclusion (another document's block, in place)✓ checked◐ iframe✓ preprocessor◐ filter
Build-time reference checking✓ error✓ warns
Raw-HTML escape hatch(by design)
Plain-text legible (no rendering)
Programmable filters / macros(by design)✓ Lua
One primitive for all blocks
Self-contained version history

The three bold rows are GEML's real differentiators. "Raw HTML = ✗" is a feature, not a gap: it keeps semantics portable and tied to no backend. Pandoc Markdown is the broadest column here — it adds citations, definition lists, and Lua filters — yet it still lacks the one primitive, build-time checking, and self-contained history.

Side-by-side syntax

Code block

GEML        === code {#hello lang=python}
            print("hi")
            ===
Markdown    ```python
            print("hi")
            ```
HTML        <pre><code class="language-python">print("hi")</code></pre>
CommonMark  ```python
            print("hi")
            ```
AsciiDoc    [source,python]
            ----
            print("hi")
            ----
Org-mode    #+begin_src python
            print("hi")
            #+end_src
Pandoc      ```{.python}
            print("hi")
            ```

Document metadata

GEML        === meta
            title = "Budget plan"
            ===
Markdown    ---                 (YAML frontmatter — convention, not spec)
            title: Budget plan
            ---
HTML        <meta name="title" content="Budget plan">
CommonMark  (no mechanism)
AsciiDoc    = Budget plan
            :version: 0.1
Org-mode    #+TITLE: Budget plan
Pandoc      ---                 (YAML metadata block — first-class)
            title: Budget plan
            ---

Admonition / callout

GEML        === note {#risks}
            Vendor lock-in is the main risk.
            ===
Markdown    > [!NOTE]            (GitHub extension)
            > Vendor lock-in is the main risk.
HTML        <div class="note">Vendor lock-in is the main risk.</div>
CommonMark  (no mechanism — plain blockquote only)
AsciiDoc    [NOTE]
            ====
            Vendor lock-in is the main risk.
            ====
Org-mode    (no standard — special block, export-dependent)
Pandoc      ::: {.note}
            Vendor lock-in is the main risk.
            :::

Cross-reference, and whether it is checked

GEML        See [[#budget]]          → #budget missing ⇒ build ERROR
Markdown    See [budget](#budget)    → broken link passes silently
HTML        See <a href="#budget">…  → not checked
CommonMark  See [budget](#budget)    → not checked
AsciiDoc    See <<budget>>           → processor WARNS on unresolved xref
Org-mode    See [[budget]]           → partially checked on export
Pandoc      See [budget](#budget)    → not checked (xref via pandoc-crossref filter)

Transclusion — content that lives in another document

GEML        === embed {src=handbook.geml#refund}
            ===                      → renders that block here. src= is
                                       reference-checked, a transclusion cycle
                                       is an error, and handbook.geml is PARSED
                                       AS ITS OWN DOCUMENT — its metadata and
                                       its relative paths resolve against
                                       itself, not against this file.
            Inline form:  ![[handbook.geml#refund]]  (inside a sentence)
Markdown    (no mechanism — Obsidian's ![[note]] is an app extension)
HTML        <iframe src="handbook.html#refund">   (a nested browsing context,
                                                   not content in this document)
CommonMark  (no mechanism)
AsciiDoc    include::handbook.adoc[tag=refund]    (preprocessor: the target's
                                                   TEXT is spliced in, and then
                                                   resolves against THIS file)
Org-mode    #+INCLUDE: "handbook.org::#refund" :only-contents t
Pandoc      (no native form — a filter, e.g. pandoc-include)

Table with computed columns (GEML-specific)

GEML        === table {#fy25 format=csv header=1}
            Segment, Q1, Q2, Q3, Q4
            Cloud,   1,  2,  3,  4
            ===
            === view {#fy25-report src=#fy25
              compute="FY [%.1f] = Q1 + Q2 + Q3 + Q4"
              summary="Segment = 'Total'; FY = sum(FY)"}
            ===                       → per-row FY column + a Total summary row,
                                        FY shown to 1 decimal
Org-mode    | Segment | Q1 | Q2 | Q3 | Q4 | FY |
            |---------+----+----+----+----+----|
            #+TBLFM: \$6=\$2+\$3+\$4+\$5    (the inspiration — but a full spreadsheet:
                                        cell refs, remote(), Emacs Lisp. GEML
                                        keeps a restricted column-formula subset)
others      static tables only — no computation

Structured data, verified (GEML-specific)

GEML        === data {#limits format=json}
            { "retries": 3, "timeout_ms": 500 }
            ===                       → parsed and VERIFIED at build time; a
                                        malformed body is an error naming the
                                        offending line. #limits is addressable,
                                        referenceable and versioned like any
                                        other block.

            === data {#events format=jsonl src=events.jsonl}
            ===                       → the log stays a plain .jsonl file every
                                        existing tool can append to and tail;
                                        the document is its verified, chartable
                                        view. A body AND src= is an error —
                                        one source, always.
others      a ```json fence — text the processor never reads, never checks, and
            cannot address, reference or chart. HTML's
            <script type="application/json"> and Org's #+NAME'd src block come
            closest, and neither verifies the payload.

Code that names its source, instead of copying it

GEML        === code {#parse lang=ts src=src/parser.ts#L14-24}
            ===                       → the block IS those lines. If the file no
                                        longer has them the build FAILS
                                        (bad-source-range); a body kept
                                        alongside src= is a snapshot, and warns
                                        when it has drifted from the source.
AsciiDoc    [source,ts]
            ----
            include::src/parser.ts[lines=14..24]
            ----                      → whatever sits at those lines today,
                                        silently — a drifted range is not an error
Org-mode    #+INCLUDE: "src/parser.ts" src ts :lines "14-25"   (same, at export)
Pandoc      ```{.ts include=src/parser.ts startLine=14 endLine=24}   (filter)
MD/HTML     copy-paste, and it rots

Diagram (hosting an external DSL)

GEML        === diagram {#flow format=mermaid}
            graph LR
              A --> B
            ===
Markdown    ```mermaid             (GitHub renders it; no id/caption/check)
            graph LR
              A --> B
            ```
AsciiDoc    [mermaid]
            ----
            graph LR
              A --> B
            ----
Org-mode    #+begin_src plantuml :file out.png
            ...
            #+end_src
Pandoc      ```{.mermaid}          (rendered by a filter, e.g. mermaid-filter)
            graph LR
              A --> B
            ```
HTML/CMark  no native diagram hosting

Chart bound to a table or a data block (GEML-specific)

GEML        === diagram {#rev format=geml-chart data=#fy25 type=bar x=Segment y=FY}
            ===                       → renders table #fy25 as a chart; column
                                        refs checked. data= takes the same three
                                        targets a table's src= does: a block in
                                        this document, doc.geml#id in another,
                                        or a file. A data block feeds a chart
                                        when its value is a record array.
others      hand-copy data into a chart lib, or a spreadsheet app — no link

What only GEML does

Every format above can render a heading and a code block. The difference is what happens to a whole document under change and automation:

  • A single typed block carries code, tables, diagrams, math, callouts, and metadata — so there is one grammar to learn and one grammar for a tool (or an LLM) to emit correctly, instead of a different syntax per feature plus an HTML fallback.
  • References are validated at build time. An #id that doesn't resolve fails the build, instead of slipping through as a dead link the way it does in Markdown/HTML.
  • Content may live elsewhere and still be checked. An embed block (or an inline ![[…]]) renders another document's block in place; src= gives a code block a line range in a real source file, a table its CSV, a data block its .jsonl log. Every named target is resolved at build time — a dangling reference, a transclusion cycle, and a line range that has drifted since the file changed all fail the build. AsciiDoc and Org-mode can include too, but they splice text: the target's own relative paths then resolve against whoever included it, and a stale line range yields the wrong lines quietly. In GEML the named document is parsed as a document in its own right, so it resolves against itself at every depth of the chain.
  • Data is verified, not quoted. A data block carries JSON's value domain — scalars, sequences, maps — as data the build parses and rejects when malformed, where a ```json fence in every other format here is text nobody reads. It takes an #id, so it can be referenced, block-edited, versioned and charted; and with src=events.jsonl the records stay a plain append-and-tail file while the document becomes their verified view.
  • History is self-contained. A sibling .gemlhistory file reconstructs any past revision and rolls the document back — offline, with no git and no online service. See the history extension.

Pandoc plays a different game — it is the universal converter, and the most practical way to reach docx/latex/epub. A natural future for GEML is to join that ecosystem (a Pandoc reader/writer) rather than compete with it.

See the core specification for the full format, and the README for a quick tour.