Markdown is a wonderful lightweight markup — minimal, easy to read and write, widely supported — but precedent-based: it codified diverse practices, so it is messy and inconsistent. StrictMark is Markdown refactored: a rational subset that keeps every feature while shrinking the grammar to its shortest formal form, for a uniform, unambiguous syntax. It reuses existing Markdown tooling without legacy weight, and is the markup every Beagle wiki page uses, from the Home overview to the Verbs vocabulary.
Markdown implementations disagree — Vim, VS Code, and the output HTML all differ — and the textbook fix, a formal grammar, is blocked by the syntax itself: every element has its own shape and those shapes interact, so N elements spawn N×N corner cases.
</element> one uniform syntax; Markdown gives each a different one, so a grammar becomes a road of pain.StrictMark makes a Markdown subset a proper markup language by formalizing its grammar — a minimal hypertext markup, not an elephantine HTML engine. Feature sprawl is held back by transclusion, not new syntax. This document is itself StrictMark.
Every line carries its markup at line start, in 4-char quads — the block stack — as (INDENT|QUOTE)* (LIST|LEAF)?; absent a leaf, a paragraph is implied. One line, one classification: a line whose prefix is off-grammar is a paragraph, and nothing later can reinterpret it.
depth*4+4); a space sitting there is content, which the renderer trims.Markdown inline markup looks easy but is not: ambiguity makes it hard to implement. StrictMark treats it all as bracketing — brackets matched by regex, effective only when they satisfy precedence. The cuts are deliberate; inline formatting is secondary by design.
(?<=\s)[*](?=\S) opens STRONG — live only if precedence allows._emphasized_ and ~struck~, [link][1], *strong*, \* escapes, ` code `.*strong*, never **strong**.Every block element in three aspects: its quad shape, its same-line content, and its deeper-line content mode. Every block ends at a dedent — the one law needs no column; the paragraph is the exception, joining same-depth lines until a blank or a marker. The div and the quote are prefix quads — repeatable, nesting containers; the rest are markers, plus the markerless paragraph.
element quad shape same line one quad deeper
--------- ---------------- ----------------- ----------------
div 4 sp / one tab rest of the stack child blocks
quote '>' + 3 sp rest of the stack child blocks
bullet '-' + 3 sp item text child blocks
numbered 1-3 digits, '.' item text child blocks
todo '-[' state ']' item text child blocks
header 1-4 '#', padded header text header text
fence 4 backticks info string verbatim bytes
ref def '[x]:' url "title" url/title text
meta pair Key + ':' verbatim value more value text
ruler 4 dashes caption text caption text
paragraph (none) inline text (same-depth join)
StrictMark allows four header levels, ATX only. The marker is a run of 1-4 # filling one quad, padded with spaces in any column; the level is the COUNT of #, and a full quad needs no gap space. A header may span lines: its text continues one quad deeper, joined into one heading.
# Top header
## Subheader
### Small header
#### Smallest header
# padding sits in any column
####Smallest, gapless
The unordered marker is the dash -; * is dropped as ambiguous and + as unpopular — there must be one way only. Ordered lists use 12. (digits then dot), again four chars per level; a TODO item is a dash plus a bracketed one-char state, -[ ], its own four-char block.
-[ ] not started, -[v] done/closed, -[-] blocked, -[x] wontfix/moot — uppercase V/X also accepted.-[v]New is a TODO item whose content is New.1. renders fine but the raw markup is then mis-numbered.<!-- --> comment — better still, put real text between them. - bulleted item
- still bulleted
1. nested numbered list
2. more numbered
plain paragraph, nested, indented 4 chars
- resume the bulleted list
Blockquote markup is one > and three spaces, in any order — a prefix quad like the indent, so quotes nest and any block sits inside one (> 1. x is a numbered item in a quote). A quoted block's continuation lines repeat the same quote prefix; there is no indent-only continuation for quotes.
> # Quoted header
> Quoted paragraph text.
A code block opens with a fence — four backticks, then optionally the info string naming the language. The body is verbatim, one quad deeper; the continuation law applies as everywhere, so a dedent ends the block and the closing fence is optional.
console.log("JavaScript is the best worst lang ever");
The ruler is a quad of four dashes, optionally followed by a caption — ---- cut at this line — that continues one quad deeper like header text. The dash run stops at four: a fifth dash makes the line a paragraph, never a ruler captioned with dashes. The legacy 3-dash form is accepted on read.
<hr>; a captioned one as a labeled divider — the caption centered in the rule line.
A meta pair is a leaf block carrying machine-readable page metadata: a capitalized three-letter key, a colon, the gap space, then the value — Who: gritzko. Key plus colon is exactly four chars, a native-width marker. The grammar fixes only the shape; each pocket registers the keys it recognizes (todo lists the ticket keys) and unknown keys are legal markup with no semantics.
KEY ':' GAP VALUE, KEY := [A-Z][a-z][a-z0-9] — matched by regex at line start, like every leaf block.Due\:.rg '^Who: gritzko' todo/ — the filename supplies the page context.The emphasis family is one bracket template: a single-character symmetric pair whose opener touches the following non-space. Code spans and escapes are the two degenerate brackets — one with a verbatim body, one with a one-char body.
*x*, emphasis _x_, strikethrough ~x~: the same shape, one delimiter char each; bodies may carry escapes (\*). x `: verbatim body, first closer wins, no escapes and no backticks inside; the body is never re-lexed.\c: the next char is literal; an escaped opener never opens, an escaped closer never closes.
StrictMark links come in exactly two cases, both defined out of line: an explicit [text text][l] pairing any display text with a one-symbol label l, and a shortcut [page] that keys on the bracket text, so a page name is its own key.
[text text][l] with [l]: url "title" — any display text, one-symbol label l (digits, then letters).[page] keys on the bracket text — [StrictMark] resolves [StrictMark]: url; the key may be multi-char.[StrictMark] becomes a link to ./StrictMark.mkd#anchor rides on an explicit label."title" — for URIs longer than a line.! — ![alt][t]; the renderer handles the foreign type. see [Replicated Object Notation][1]
[1]: http://doc.replicated.cc/ron.sm "What is RON"
a bare [StrictMark] link, defined below
[StrictMark]: StrictMark.mkd "the markup"
![here is the table][T]
[T]: /table?@tab "this might be any object"
StrictMark is a CommonMark subset in spirit and most pages render fine on GitHub raw; the divergences below are declared, and a few writing conventions keep the rendered output aligned. When the two disagree, StrictMark wins.
#### x, -[v] x, 12. x) — the renderer trims it, and CommonMark requires it.-[v] as plain text.The structural layer is a regular language and the inline layer is regex-matched brackets, so the whole parser stays compact. The canonical grammar lives in the Beagle source, not in prose — the block machine and the inline Ragel machine below.
mkdtb (dog/tok/MKDTB.c.rl) splits each line into quads / mark / rest; MKDTLexer in dog/tok/MKDT.c drives it.MKDTInlineLexer (Ragel, dog/tok/MKDT.c.rl) matches code, strong, emph, strike, links, transclusions as regexes.