MDC Syntax Logo
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."
]
[Link text](https://example.com)
["a", { "href": "https://example.com" }, "Link text"]

Images

![Alt text](image.png)
["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

  1. Array-based format: More memory efficient than object-based AST
  2. Shallow iteration: Children start at index 2, making iteration predictable
  3. Immutable by convention: Create new arrays when modifying
  4. Lazy processing: Process only what you need

See Also