Syntax
Minimark AST
Complete reference for the Minimark AST (Abstract Syntax Tree) format used by MDC Syntax.
Overview
Minimark is a lightweight, array-based AST format designed for efficient processing and minimal memory usage. Unlike traditional node-based ASTs, Minimark uses nested arrays to represent document structure.
AST Structure
MinimarkTree
The root container for all parsed content:
interface MinimarkTree {
type: 'minimark'
value: MinimarkNode[]
}
MinimarkNode
Each node is either a string (text) or a tuple array:
type MinimarkNode =
| string // Text content
| [tag: string, props?: Record<string, any>, ...children: MinimarkNode[]]
Node Format
Text Nodes
Plain strings represent text content:
"Hello, World!"
Element Nodes
Elements are arrays with the format [tag, props, ...children]:
["p", {}, "This is a paragraph"]
Components:
- tag (index 0): Element name or component tag
- props (index 1): Object with attributes/properties
- children (index 2+): Child nodes (strings or nested arrays)
Common Elements
Headings
# Heading 1
## Heading 2
["h1", { "id": "heading-1" }, "Heading 1"],
["h2", { "id": "heading-2" }, "Heading 2"]
Paragraphs
This is a paragraph with **bold** and *italic* text.
["p", {},
"This is a paragraph with ",
["strong", {}, "bold"],
" and ",
["em", {}, "italic"],
" text."
]
Links
[Link text](https://example.com)
["a", { "href": "https://example.com" }, "Link text"]
Images

["img", { "src": "image.png", "alt": "Alt text" }]
Lists
- Item 1
- Item 2
- Nested item
["ul", {},
["li", {}, "Item 1"],
["li", {},
"Item 2",
["ul", {},
["li", {}, "Nested item"]
]
]
]
Ordered Lists
1. First
2. Second
["ol", {},
["li", {}, "First"],
["li", {}, "Second"]
]
Code Blocks
```javascript [app.js] {1-2}
const a = 1
const b = 2
```
["pre", {
"language": "javascript",
"filename": "app.js",
"highlights": [1, 2]
},
["code", { "class": "language-javascript" },
"const a = 1\nconst b = 2"
]
]
Inline Code
Use `const` for constants
["p", {},
"Use ",
["code", {}, "const"],
" for constants"
]
Blockquotes
> This is a quote
["blockquote", {},
["p", {}, "This is a quote"]
]
Tables
| Header 1 | Header 2 |
| -------- | -------- |
| Cell 1 | Cell 2 |
["table", {},
["thead", {},
["tr", {},
["th", {}, "Header 1"],
["th", {}, "Header 2"]
]
],
["tbody", {},
["tr", {},
["td", {}, "Cell 1"],
["td", {}, "Cell 2"]
]
]
]
Horizontal Rule
---
["hr", {}]
MDC Components
Block Components
::alert{type="info"}
This is an alert message
::
["alert", { "type": "info" },
["p", {}, "This is an alert message"]
]
Inline Components
Check out this :badge[New]{color="blue"} feature
["p", {},
"Check out this ",
["badge", { "color": "blue" }, "New"],
" feature"
]
Components with Slots
::card
#header
## Card Title
#content
Main content here
::
["card", {},
["template", { "name": "header" },
["h2", { "id": "card-title" }, "Card Title"]
],
["template", { "name": "content" },
["p", {}, "Main content here"]
]
]
Nested Components
:::outer
::inner
Content
::
:::
["outer", {},
["inner", {},
["p", {}, "Content"]
]
]
Property Types
String Properties
::component{title="Hello"}
["component", { "title": "Hello" }]
Boolean Properties
::component{disabled}
["component", { ":disabled": "true" }]
Note: Boolean props are prefixed with : in the AST.
Number Properties
::component{:count="5"}
["component", { ":count": "5" }]
Object/Array Properties
::component{:data='{"key": "value"}'}
["component", { ":data": "{\"key\": \"value\"}" }]
ID and Class Properties
::component{#my-id .class-one .class-two}
["component", {
"id": "my-id",
"class": "class-one class-two"
}]
Complete AST Example
Input Markdown:
---
title: Example Document
author: John Doe
---
# Welcome
This is a **sample** document with [links](https://example.com).
::alert{type="info"}
Important notice
::
## Code Example
\`\`\`javascript [demo.js]
console.log("Hello")
\`\`\`
Output AST:
{
"type": "minimark",
"value": [
["h1", { "id": "welcome" }, "Welcome"],
["p", {},
"This is a ",
["strong", {}, "sample"],
" document with ",
["a", { "href": "https://example.com" }, "links"],
"."
],
["alert", { "type": "info" },
["p", {}, "Important notice"]
],
["h2", { "id": "code-example" }, "Code Example"],
["pre", { "language": "javascript", "filename": "demo.js" },
["code", { "class": "language-javascript" },
"console.log(\"Hello\")"
]
]
]
}
Frontmatter Data:
{
"title": "Example Document",
"author": "John Doe"
}
Working with AST
Traversing Nodes
import type { MinimarkNode, MinimarkTree } from 'mdc-syntax'
function traverse(node: MinimarkNode, callback: (node: MinimarkNode) => void) {
callback(node)
if (Array.isArray(node)) {
const children = node.slice(2)
for (const child of children) {
traverse(child, callback)
}
}
}
// Usage
const result = parse(content)
traverse(result.body.value[0], (node) => {
if (Array.isArray(node)) {
console.log('Element:', node[0])
} else {
console.log('Text:', node)
}
})
Finding Elements
function findByTag(tree: MinimarkTree, tag: string): MinimarkNode[] {
const results: MinimarkNode[] = []
function search(node: MinimarkNode) {
if (Array.isArray(node) && node[0] === tag) {
results.push(node)
}
if (Array.isArray(node)) {
node.slice(2).forEach(search)
}
}
tree.value.forEach(search)
return results
}
// Find all headings
const headings = findByTag(result.body, 'h1')
Modifying AST
function addClassToLinks(node: MinimarkNode): MinimarkNode {
if (Array.isArray(node)) {
const [tag, props, ...children] = node
if (tag === 'a') {
return [tag, { ...props, class: 'external-link' }, ...children]
}
return [tag, props, ...children.map(addClassToLinks)]
}
return node
}
// Transform all links
const transformed = {
type: 'minimark',
value: result.body.value.map(addClassToLinks)
}
Extracting Text Content
function extractText(node: MinimarkNode): string {
if (typeof node === 'string') {
return node
}
if (Array.isArray(node)) {
return node.slice(2).map(extractText).join('')
}
return ''
}
// Get all text from a heading
const heading = ['h1', { id: 'hello' }, 'Hello ', ['strong', {}, 'World']]
console.log(extractText(heading)) // "Hello World"
Rendering AST
To HTML
import { renderHTML } from 'mdc-syntax'
const result = parse('# Hello **World**')
const html = renderHTML(result.body)
// <h1 id="hello-world">Hello <strong>World</strong></h1>
To Markdown
import { renderMarkdown } from 'mdc-syntax'
const result = parse('# Hello **World**')
const markdown = renderMarkdown(result.body)
// # Hello **World**
Custom Rendering
function renderToPlainText(node: MinimarkNode): string {
if (typeof node === 'string') {
return node
}
if (Array.isArray(node)) {
const [tag, props, ...children] = node
const content = children.map(renderToPlainText).join('')
// Add formatting based on tag
switch (tag) {
case 'h1':
case 'h2':
case 'h3':
return `${content}\n\n`
case 'p':
return `${content}\n\n`
case 'li':
return `• ${content}\n`
default:
return content
}
}
return ''
}
TypeScript Types
import type {
MinimarkTree,
MinimarkNode,
ParseResult
} from 'mdc-syntax'
// Type guard for element nodes
function isElement(node: MinimarkNode): node is [string, Record<string, any>, ...MinimarkNode[]] {
return Array.isArray(node) && typeof node[0] === 'string'
}
// Type guard for text nodes
function isText(node: MinimarkNode): node is string {
return typeof node === 'string'
}
// Usage
function processNode(node: MinimarkNode) {
if (isText(node)) {
console.log('Text:', node)
} else if (isElement(node)) {
const [tag, props, ...children] = node
console.log('Element:', tag, props)
}
}
Performance Considerations
- Array-based format: More memory efficient than object-based AST
- Shallow iteration: Children start at index 2, making iteration predictable
- Immutable by convention: Create new arrays when modifying
- Lazy processing: Process only what you need
See Also
- Parse API - Generating Minimark AST
- Markdown Syntax - MDC syntax reference
- Vue Renderer - Rendering AST in Vue
- React Renderer - Rendering AST in React