Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

mq is a command-line tool that processes Markdown using a syntax similar to jq. It’s written in Rust, allowing you to easily slice, filter, map, and transform structured data.

Why mq?

mq makes working with Markdown files as easy as jq makes working with JSON. It’s especially useful for:

  • LLM Workflows: Efficiently manipulate and process Markdown used in LLM prompts and outputs
  • Documentation Management: Extract, transform, and organize content across multiple documentation files
  • Content Analysis: Quickly extract specific sections or patterns from Markdown documents
  • Batch Processing: Apply consistent transformations across multiple Markdown files

Features

  • Slice and Filter: Extract specific parts of your Markdown documents with ease.
  • Map and Transform: Apply transformations to your Markdown content.
  • Command-line Interface: Simple and intuitive CLI for quick operations.
  • Extensibility: Easily extendable with custom functions.
  • Built-in support: Filter and transform content with many built-in functions and selectors.
  • REPL Support: Interactive command-line REPL for testing and experimenting.
  • IDE Support: VSCode Extension and Language Server Protocol (LSP) support for custom function development.

Getting Started

Getting Started

This section guides you through the installation of mq.

Install

Quick Install

curl -sSL https://mqlang.org/install.sh | bash
# Install the debugger
curl -sSL https://mqlang.org/install.sh | bash -s -- --with-debug

The installer will:

  • Download the latest mq binary for your platform
  • Install it to ~/.mq/bin/
  • Update your shell profile to add mq to your PATH

Cargo

# Install from crates.io
cargo install mq-run
# Install from Github
cargo install --git https://github.com/harehare/mq.git mq-run --tag v0.5.17
# Latest Development Version
cargo install --git https://github.com/harehare/mq.git mq-run --bin mq
# Install the debugger
cargo install --git https://github.com/harehare/mq.git mq-run --bin mq-dbg --features="debugger"
# Install using binstall
cargo binstall [email protected]

Binaries

You can download pre-built binaries from the GitHub releases page:

# macOS (Apple Silicon)
curl -L https://github.com/harehare/mq/releases/download/v0.5.17/mq-aarch64-apple-darwin -o /usr/local/bin/mq && chmod +x /usr/local/bin/mq
# Linux x86_64
curl -L https://github.com/harehare/mq/releases/download/v0.5.17/mq-x86_64-unknown-linux-gnu -o /usr/local/bin/mq && chmod +x /usr/local/bin/mq
# Linux arm64
curl -L https://github.com/harehare/mq/releases/download/v0.5.17/mq-aarch64-unknown-linux-gnu -o /usr/local/bin/mq && chmod +x /usr/local/bin/mq
# Windows (PowerShell)
Invoke-WebRequest -Uri https://github.com/harehare/mq/releases/download/v0.5.17/mq-x86_64-pc-windows-msvc.exe -OutFile "$env:USERPROFILE\bin\mq.exe"

Homebrew

# Using Homebrew (macOS and Linux)
$ brew install mq

Docker

$ docker run --rm ghcr.io/harehare/mq:0.5.17

mq-lsp (Language Server)

The mq Language Server provides IDE features like completion, hover, and diagnostics for mq query files.

Quick Install

curl -sSL https://mqlang.org/install_lsp.sh | bash

Cargo

# Install from crates.io
cargo install mq-lsp
# Install from Github
cargo install --git https://github.com/harehare/mq.git mq-lsp --tag v0.5.17
# Latest Development Version
cargo install --git https://github.com/harehare/mq.git mq-lsp
# Install using binstall
cargo binstall [email protected]

Binaries

You can download pre-built binaries from the GitHub releases page:

# macOS (Apple Silicon)
curl -L https://github.com/harehare/mq/releases/download/v0.5.17/mq-lsp-aarch64-apple-darwin -o /usr/local/bin/mq-lsp && chmod +x /usr/local/bin/mq-lsp
# Linux x86_64
curl -L https://github.com/harehare/mq/releases/download/v0.5.17/mq-lsp-x86_64-unknown-linux-gnu -o /usr/local/bin/mq-lsp && chmod +x /usr/local/bin/mq-lsp
# Linux arm64
curl -L https://github.com/harehare/mq/releases/download/v0.5.17/mq-lsp-aarch64-unknown-linux-gnu -o /usr/local/bin/mq-lsp && chmod +x /usr/local/bin/mq-lsp
# Windows (PowerShell)
Invoke-WebRequest -Uri https://github.com/harehare/mq/releases/download/v0.5.17/mq-lsp-x86_64-pc-windows-msvc.exe -OutFile "$env:USERPROFILE\bin\mq-lsp.exe"

Visual Studio Code Extension

You can install the VSCode extension from the Visual Studio Marketplace.

Neovim

You can install the Neovim plugin by following the instructions in the mq.nvim README.

GitHub Actions

You can use mq in your GitHub Actions workflows with the Setup mq action:

steps:
  - uses: actions/checkout@v4
  - uses: harehare/setup-mq@v1
  - run: mq '.code' README.md

MCP (Model Context Protocol) server

mq supports an MCP server for integration with LLM applications.

See the MCP documentation for more information.

Python

You can use mq in Python through the markdown-query package:

# Install from PyPI
$ pip install markdown-query

npm

You can use mq in npm through the mq-web package:

$ npm i mq-web

Web crawler

# Using Homebrew (macOS and Linux)
$ brew install harehare/tap/mqcr

Development

Prerequisites

Setting up the development environment

Clone the repository:

git clone https://github.com/harehare/mq.git
cd mq

Install development dependencies:

# Using cargo
cargo install just wasm-pack

Or if you prefer using asdf:

# Using asdf
asdf install

Common development tasks

Here are some useful commands to help you during development:

# Run the CLI with the provided arguments
just run '.code'

# Run formatting, linting and all tests
just test

# Run formatter and linter
just lint

# Build the project in release mode
just build

# Update documentation
just docs

Check the just --list for more available commands and build options.

Syntax Highlighting

Using bat

bat is a cat clone with syntax highlighting and Git integration. You can use mq’s Sublime syntax file to enable syntax highlighting for mq files in bat.

Setting up mq syntax highlighting

  1. Create the bat syntax directory if it doesn’t exist:
mkdir -p "$(bat --config-dir)/syntaxes"
  1. Copy the mq syntax file:
# Clone the mq repository or download mq.sublime-syntax
curl -o "$(bat --config-dir)/syntaxes/mq.sublime-syntax" \
  https://raw.githubusercontent.com/harehare/mq/main/assets/mq.sublime-syntax
  1. Rebuild bat’s cache:
bat cache --build

Usage

Now you can use bat to display mq files with syntax highlighting:

# View an mq file with syntax highlighting
bat query.mq

Example

Create a sample mq file:

cat > example.mq << 'EOF'
# This is a comment
def greet(name):
  s"Hello, ${name}!"
end

.h | .text | greet("World")
EOF

View it with syntax highlighting:

bat example.mq

Editor Support

In addition to bat, mq syntax highlighting is available for:

Playground

An Online Playground is available, powered by WebAssembly.

Interactive TUI

The Text-based User Interface (TUI) provides an interactive way to explore and query Markdown files directly in your terminal.

Quick Install

curl -fsSL https://raw.githubusercontent.com/harehare/mq-tui/main/bin/install.sh | bash
$ mq tui file.md

TUI Features

  • Interactive Querying: Enter and edit queries in real-time with immediate feedback
  • Detail View: Examine the structure of selected markdown nodes in depth
  • Navigation: Browse through query results with keyboard shortcuts
  • Query History: Access and reuse previous queries

TUI Key Bindings

KeyAction
: (colon)Enter query mode
EnterExecute query
Esc / qExit query mode / Exit app
/k, /jNavigate results
dToggle detail view
? / F1Show help screen
Ctrl+lClear query
PgUp/PgDnPage through results
Home/EndJump to first/last result

Repository

The source code and further documentation for mqt are available on GitHub:

https://github.com/harehare/mqt

Web crawler

mq-crawler is a web crawler that fetches HTML content from websites, converts it to Markdown format, and processes it with mq queries. It’s distributed as the mqcr binary.

Key Features

  • HTML to Markdown conversion: Automatically converts crawled HTML pages to clean Markdown
  • robots.txt compliance: Respects robots.txt rules for ethical web crawling
  • mq-lang integration: Processes content with mq-lang queries for filtering and transformation
  • Configurable crawling: Customizable delays, domain restrictions, and link discovery
  • Flexible output: Save to files or output to stdout

Installation

# Using Homebrew (macOS and Linux)
$ brew install harehare/tap/mqcr

Usage

mqcr [OPTIONS] <URL>

Options

  • -o, --output <OUTPUT>: Directory to save markdown files (stdout if not specified)
  • -c, --crawl-delay <CRAWL_DELAY>: Delay between requests in seconds (default: 1)
  • --robots-path <ROBOTS_PATH>: Custom robots.txt URL
  • -m, --mq-query <MQ_QUERY>: mq-lang query for processing content (default: identity())

Examples

# Basic crawling to stdout
mqcr https://example.com

# Save to directory with custom delay
mqcr -o ./output -c 2 https://example.com

# Process with mq-lang query
mqcr -m '.h | select(contains("News"))' https://example.com

MCP

The mq MCP server enables integration with AI applications that support the Model Context Protocol (MCP). This server provides tools for processing Markdown content using mq queries.

Overview

The MCP server exposes four main tools:

  • html_to_markdown - Converts HTML to Markdown and applies mq queries
  • extract_markdown - Extracts content from Markdown using mq queries
  • available_functions - Lists available mq functions
  • available_selectors - Lists available mq selectors

Configuration

Claude Desktop

Add the following to your Claude Desktop configuration file:

{
  "mcpServers": {
    "mq": {
      "command": "/path/to/mq",
      "args": ["mcp"]
    }
  }
}

Claude Code

$ claude mcp add mq-mcp -- mq mcp

VS Code

Add the following to your VS Code settings:

{
  "mcp": {
    "servers": {
      "mq-mcp": {
        "type": "stdio",
        "command": "/path/to/mq",
        "args": ["mcp"]
      }
    }
  }
}

Replace /path/to/mq with the actual path to your mq binary.

Usage

Converting HTML to Markdown

The html_to_markdown tool converts HTML content to Markdown format and applies an optional mq query:

html_to_markdown({
  "html": "<h1>Title</h1><p>Content</p>",
  "query": ".h1"
})

Extracting from Markdown

The extract_markdown tool processes Markdown content with mq queries:

extract_markdown({
  "markdown": "# Title\n\nContent",
  "query": ".h1"
})

Getting Available Functions

The available_functions tool returns all available mq functions:

available_functions()

Returns JSON with function names, descriptions, parameters, and examples.

Getting Available Selectors

The available_selectors tool returns all available mq selectors:

available_selectors()

Returns JSON with selector names, descriptions, and parameters.

Query Examples

Common mq queries you can use with the MCP tools:

  • .h1 - Select all h1 headings
  • select(.code.lang == "js") - Select JavaScript code blocks
  • .text - Extract all text content
  • select(.h1, .h2) - Select h1 and h2 headings
  • select(not(.code)) - Select everything except code blocks

Debugger

The mq debugger allows you to step through execution, set breakpoints, and inspect the state of your mq programs during runtime. This is particularly useful for debugging complex queries and understanding how data flows through your transformations.

Installation

To use the debugger, you need to install mq with the debugger feature enabled.

Quick Install

curl -sSL https://mqlang.org/install.sh | bash -s -- --with-debug

Cargo

You can do this by building from source:

cargo install --git https://github.com/harehare/mq.git mq-run --bin mq-dbg

Alternatively, if a prebuilt binary is available for your platform, download it from the releases page and ensure it is in your PATH.

Homebrew

If you use Homebrew, you can install the debugger-enabled mq with:

brew install harehare/tap/mq-dbg

Getting Started

The debugger is available through the mq-dbg binary when the debugger feature is enabled.

# Enable debugging for an mq script
mq-dbg -f your-script.mq input.md

Debugger Interface

Once the debugger starts, you’ll see a prompt (mqdbg) where you can enter debugging commands. The debugger will automatically display the current source code location with line numbers, highlighting the current execution point.

   10| def process_headers() {
=> 11|   . | select(.type == "heading")
   12|     | map(.level)
   13| }
(mqdbg)

Available Commands

The debugger supports the following commands:

CommandAliasDescription
stepsStep into the next expression, diving into function calls
nextnStep over the current expression, skipping over function calls
finishfRun until the current function returns
continuecContinue normal execution until the next breakpoint

Breakpoint Commands

CommandAliasDescription
breakpoint [line]b [line]Set a breakpoint at the specified line number
breakpointbList all active breakpoints
clear [id]cl [id]Clear a specific breakpoint by ID
clearclClear all breakpoints

Inspection Commands

CommandAliasDescription
infoiDisplay current environment variables and context
listlShow source code around the current execution point
long-listllShow the entire source code with line numbers
backtracebtPrint the current call stack

Control Commands

CommandAliasDescription
help-Display help information for all commands
quitqQuit the debugger and exit

Setting Breakpoints

You can set breakpoints in several ways:

Interactive Breakpoints

You can set breakpoints interactively from the debugger prompt:

(mqdbg) breakpoint 15

(mqdbg) breakpoint
Breakpoints:
  [1] 15:10 (enabled)

Programmatic Breakpoints

You can also set breakpoints directly in your mq code using the breakpoint() function:

def process_data(items) {
   breakpoint()  # Execution will pause here when debugger is attached
   | items | filter(fn(item): item == "test")
}

When the debugger encounters a breakpoint() function call during execution, it will automatically pause and enter interactive debugging mode.

Note: The breakpoint() function only has an effect when running under the debugger (mq-dbg). In normal execution (mq), it is ignored and has no impact on performance.

External Subcommands

You can extend mq with custom subcommands by placing executable files starting with mq- in ~/.mq/bin/ or anywhere in your PATH.

Creating a Custom Subcommand

Create an executable file with the mq- prefix in ~/.mq/bin/ or a directory on your PATH:

# Create a custom subcommand
cat > ~/.mq/bin/mq-hello << 'EOF'
#!/bin/bash
echo "Hello from mq-hello!"
echo "Arguments: $@"
EOF
chmod +x ~/.mq/bin/mq-hello

# Use the custom subcommand
mq hello world
# Output: Hello from mq-hello!
#         Arguments: world

Command Resolution

When you run mq <subcommand>, mq searches for an executable named mq-<subcommand> in the following order:

  1. ~/.mq/bin/ directory
  2. Directories in PATH

The first match found is used.

Listing Available Subcommands

Use the --list flag to see all available subcommands:

mq --list

This makes it easy to add your own tools and workflows to mq without modifying the core binary.

External Tools

The following external tools are available to extend mq’s functionality:

  • mq-check - A syntax and semantic checker for mq files.
  • mq-conv - A CLI tool for converting various file formats to Markdown.
  • mq-docs - A documentation generator for mq functions, macros, and selectors.
  • mq-edit - A terminal-based Markdown and code editor with WYSIWYG rendering and LSP support.
  • mq-mcp - Model Context Protocol (MCP) server implementation for AI assistants.
  • mq-task - Task runner using mq for Markdown-based task definitions.
  • mq-tui - Terminal User Interface (TUI) for interactive mq query.
  • mq-update - Update mq binary to the latest version.
  • mq-view - Viewer for Markdown content.

Example

This page demonstrates practical examples of mq queries for common Markdown processing tasks. Each example includes the query, explanation, and typical use cases.

Basic Element Selection

Select All Headings

Extract all headings from a markdown document:

.h

Input example:

# Main Title
## Section 1
### Subsection 1.1
## Section 2

Output: Returns all heading elements with their levels and text.

Extract Specific Table Row

Extract the second row from a markdown table:

.[1][]

Input example:

| Name  | Age | City |
| ----- | --- | ---- |
| Alice | 30  | NYC  |
| Bob   | 25  | LA   |

Output: Returns ["Bob", "25", "LA"]

Extract Specific List

Extract the second list from the document:

.[1]

Code Block Operations

Exclude Code Blocks

Filter out all code blocks from a document, keeping only prose content:

select(!.code)

Input example:

This is a paragraph.

```js
console.log("code");
```

Another paragraph.

Output: Returns only the paragraph elements, excluding the code block.

Extract JavaScript Code Blocks

Select only code blocks with a specific language:

select(.code.lang == "js")

Input example:

```js
const x = 1;
```

```python
x = 1
```

```js
const y = 2;
```

Output: Returns only the two JavaScript code blocks.

Extract Language Names

Get a list of all programming languages used in code blocks:

.code.lang

Example output: ["js", "python", "rust", "bash"]

Extract MDX Components

Select all MDX components (JSX-like elements in Markdown):

select(is_mdx())

Input example:

Regular paragraph.

<CustomComponent prop="value" />

Another paragraph.

<AnotherComponent>
  Content
</AnotherComponent>

Output: Returns only the MDX component elements.

Get all URLs from markdown links:

.link.url

Input example:

Check out [mq](https://mqlang.org) and [GitHub](https://github.com).

Example output: ["https://mqlang.org", "https://github.com"]

Advanced Markdown Processing

Generate Table of Contents

Create a hierarchical table of contents from headings:

.h
| let link = to_link("#" + to_text(self), to_text(self), "")
| let level = .h.depth
| if (!is_none(level)): to_md_list(link, level - 1)

Input example:

# Introduction
## Getting Started
### Installation
## Usage

Output:

- [Introduction](#introduction)
  - [Getting Started](#getting-started)
    - [Installation](#installation)
  - [Usage](#usage)

Generate XML Sitemap

Create an XML sitemap from markdown files:

def sitemap(item, base_url):
    let path = replace(to_text(item), ".md", ".html")
    | let loc = base_url + path
    | s"<url>
  <loc>${loc}</loc>
  <priority>1.0</priority>
  </url>"
end

Usage example:

$ mq 'sitemap(__FILE__, "https://example.com")' docs/**/*.md

Example output:

<url>
  <loc>https://example.com/docs/intro.html</loc>
  <priority>1.0</priority>
</url>

Section Operations

Extract Sections by Title

Extract sections whose title contains a specific text:

include "section" | nodes | section("Installation")

Input example:

# Introduction

Welcome to the project.

## Installation

Run the following command:

## Usage

Use the tool like this.

Output: Returns the “Installation” section with its header and content.

Split Document by Header Level

Split a document into sections based on header level:

include "section" | nodes | split(2)

Input example:

# Main Title

## Section 1

Content of section 1.

## Section 2

Content of section 2.

## Section 3

Content of section 3.

Output: Returns an array of section objects split at ## headers.

Generate Table of Contents from Sections

Generate a table of contents from sections:

include "section" | nodes | sections() | toc()

Input example:

# Introduction

Some text.

## Getting Started

More text.

### Prerequisites

Details here.

## Advanced Usage

Advanced content.

Output: ["- Introduction", " - Getting Started", " - Prerequisites", " - Advanced Usage"]

Filter Sections with Content

Filter sections that have content beyond the header:

include "section" | nodes | sections() | filter(fn(s): has_content(s);) | titles()

Input example:

# Introduction

Welcome to the project.

## Empty Section

## Usage

Use the tool like this.

Output: ["Introduction", "Usage"]

Custom Functions and Programming

Define Custom Function

Create reusable functions for complex transformations:

def snake_to_camel(x):
  let words = split(x, "_")
  | foreach (word, words):
      let first_char = upcase(first(word))
      | let rest_str = downcase(slice(word, 1, len(word)))
      | s"${first_char}${rest_str}";
  | join("")
end
| snake_to_camel("hello_world")

Example input: "user_name" Example output: "UserName"

Map Over Arrays

Transform each element in an array:

map([1, 2, 3, 4, 5], fn(x): x + 1;)

Example output: [2, 3, 4, 5, 6]

Filter Arrays

Select elements that meet a condition:

filter([5, 15, 8, 20, 3], fn(x): x > 10;)

Example output: [15, 20]

Fold Arrays

Combine array elements into a single value:

fold([1, 2, 3, 4], 0, fn(acc, x): acc + x;)

Example output: 10

File Processing

CSV to Markdown Table

Convert CSV data to a formatted markdown table:

$ mq 'include "csv" | csv_parse(true) | csv_to_markdown_table()' example.csv

Use case: Convert spreadsheet data to markdown format for documentation. The csv_parse(true) treats the first row as headers.

Input example (example.csv):

Name,Age,City
Alice,30,NYC
Bob,25,LA

Example output:

| Name  | Age | City |
| ----- | --- | ---- |
| Alice | 30  | NYC  |
| Bob   | 25  | LA   |

Merge Multiple Files

Combine multiple markdown files with file path separators:

$ mq -S 's"\n${__FILE__}\n"' 'identity()' docs/books/**/**.md

The -S flag adds a separator between files, and __FILE__ is a special variable containing the current file path.

Example output:

docs/intro.md

# Introduction
...

docs/usage.md

# Usage
...

Process Files in Parallel

Process large numbers of files efficiently:

$ mq -P 5 '.h1' docs/**/*.md

LLM Workflows

Extract Context for LLM Prompts

Extract specific sections to create focused context for LLM inputs:

select(.h || .code) | self[:10]

Example: Extract first 10 sections with headings or code for a code review prompt.

Document Statistics

$ mq -A 'let headers = count_by(fn(x): x | select(.h);)
| let paragraphs = count_by(fn(x): x | select(.text);)
| let code_blocks = count_by(fn(x): x | select(.code);)
| let links = count_by(fn(x): x | select(.link);)
| s"Headers: ${headers}, Paragraphs: ${paragraphs}, Code: ${code_blocks}, Links: ${links}"'' docs/books/**/**.md

Generate Documentation Index

.h
| let level = .h.level
| let text = to_text(self)
| let indent = repeat("  ", level - 1)
| let anchor = downcase(replace(text, " ", "-"))
| if (!is_empty(text)): s"${indent}- [${text}](#${anchor})"

Frontmatter Operations

Extract frontmatter metadata from markdown files:

import "yaml" | if (.yaml): yaml::yaml_parse() | get(:title)

Reference

Reference

This is a reference documentation for the mq.

CLI

The mq command-line interface provides tools for querying and manipulating markdown content. Below is the complete reference for all available commands and options.

Usage: mq [OPTIONS] [QUERY OR FILE] [FILES]... [COMMAND]

Commands:
  repl  Start a REPL session for interactive query execution
  fmt   Format mq files based on specified formatting options
  help  Print this message or the help of the given subcommand(s)

Arguments:
  [QUERY OR FILE]  
  [FILES]...       

Options:
  -A, --aggregate
          Aggregate all input files/content into a single array
  -f, --from-file
          load filter from the file
  -I, --input-format <INPUT_FORMAT>
          Set input format [possible values: markdown, mdx, html, text, null, raw]
  -L, --directory <MODULE_DIRECTORIES>
          Search modules from the directory
  -M, --module-names <MODULE_NAMES>
          Load additional modules from specified files
      --args <NAME> <VALUE>
          Sets string that can be referenced at runtime
      --rawfile <NAME> <FILE>
          Sets file contents that can be referenced at runtime
      --stream
          Enable streaming mode for processing large files line by line
      --json
          Include the built-in JSON module
      --csv
          Include the built-in CSV module
      --fuzzy
          Include the built-in Fuzzy module
      --yaml
          Include the built-in YAML module
      --toml
          Include the built-in TOML module
      --xml
          Include the built-in XML module
      --test
          Include the built-in test module
  -F, --output-format <OUTPUT_FORMAT>
          Set output format [default: markdown] [possible values: markdown, html, text, json, none]
  -U, --update
          Update the input markdown (aliases: -i, --in-place, --inplace)
      --unbuffered
          Unbuffered output
      --list-style <LIST_STYLE>
          Set the list style for markdown output [default: dash] [possible values: dash, plus, star]
      --link-title-style <LINK_TITLE_STYLE>
          Set the link title surround style for markdown output [default: double] [possible values: double, single, paren]
      --link-url-style <LINK_URL_STYLE>
          Set the link URL surround style for markdown links [default: none] [possible values: none, angle]
  -S, --separator <QUERY>
          Specify a query to insert between files as a separator
  -o, --output <FILE>
          Output to the specified file
  -C, --color-output
          Colorize markdown output
      --list
          List all available subcommands (built-in and external)
  -P <PARALLEL_THRESHOLD>
          Number of files to process before switching to parallel processing [default: 10]
  -h, --help
          Print help
  -V, --version
          Print version

# Examples:

## To filter markdown nodes:
mq 'query' file.md

## To read query from file:
mq -f 'file' file.md

## To start a REPL session:
mq repl

## To format mq file:
mq fmt --check file.mq

Types and Values

Values

  • 42 (a number)
  • "Hello, world!" (a string)
  • :value (a symbol)
  • [1, 2, 3], array(1, 2, 3) (an array)
  • {"a": 1, "b": 2, "c": 3}, dict(["a", 1], ["b", 2], ["c", 3]) (a dictionary)
  • true, false (a boolean)
  • None

Types

TypeDescriptionExamples
NumberRepresents numeric values.1, 3.14, -42
StringRepresents sequences of characters, including Unicode code points and escape sequences in the form of \{0x000}."hello", "123", "😊", "\u{1F600}"
SymbolRepresents immutable, interned identifiers prefixed with :. Used for constant values and keys.:value, :success, :error, :ok
BooleanRepresents truth values.true, false
ArrayRepresents ordered collections of values.[1, 2, 3], array(1, 2, 3)
DictRepresents key-value mappings (dictionaries).{"a": 1, "b": 2}, dict(["a", 1], ["b", 2])
FunctionRepresents executable code.def foo(): 42; let name = def foo(): 42;

Accessing Values

Array Index Access

Arrays can be accessed using square bracket notation with zero-based indexing:

let arr = [1, 2, 3, 4, 5]

arr[0]     # Returns 1 (first element)
arr[2]     # Returns 3 (third element)
arr[6]     # Returns None

You can also use the get function explicitly:

get(arr, 0)    # Same as arr[0]
arr | get(2)    # Same as arr[2]

Array Slice Access

Arrays support slice notation to extract subarrays using the arr[start:end] syntax:

let arr = [1, 2, 3, 4, 5]

arr[1:4]    # Returns [2, 3, 4] (elements from index 1 to 3)
arr[0:3]    # Returns [1, 2, 3] (first three elements)
arr[2:5]    # Returns [3, 4, 5] (elements from index 2 to end)

Slice indices work as follows:

  • start: The starting index (inclusive)
  • end: The ending index (exclusive)
  • Both indices are zero-based
  • If start or end is out of bounds, it will be clamped to valid range
let arr = [1, 2, 3, 4, 5]

arr[0:2]    # Returns [1, 2]
arr[3:10]   # Returns [4, 5] (end index clamped to array length)
arr[2:2]    # Returns [] (empty slice when start equals end)

Dictionary Key Access

Dictionaries can be accessed using square bracket notation with keys:

let d = {"name": "Alice", "age": 30, "city": "Tokyo"}

d["name"]   # Returns "Alice"
d["age"]    # Returns 30
d["city"]   # Returns "Tokyo"

You can also use the get function explicitly:

get(d, "name")   # Same as di["name"]
d | get("age")    # Same as d["age"]

Dynamic Access

Both arrays and dictionaries support dynamic access using variables:

let arr = [10, 20, 30]
| let index = 1
| arr[index]     # Returns 20

let d = {"x": 100, "y": 200}
| let key = "x"
| d[key]      # Returns 100

Environment Variables

A module handling environment-specific functionality.

  • __FILE__: Contains the path to the file currently being processed.
  • __FILE_NAME__: Contains the name of the file currently being processed (without the path).
  • __FILE_STEM__: Contains the stem of the file currently being processed (filename without extension).

Conditionals and Comparisons

Conditionals and Comparisons

Conditional expressions and comparison operators in mq allow for decision-making based on the evaluation of conditions, enabling dynamic behavior in your queries.

Conditionals

mq supports standard conditional operations through the following functions:

  • and(a, b), a && b - Returns true if both a and b are true
  • or(a, b), a || b - Returns true if either a or b is true
  • not(a), !a - Returns true if a is false

Examples

# Basic comparisons
and(true, true, true)
true && true && true
# => true
or(true, false, true)
true || false || true
# => true
not(false)
!false
# => true

Comparisons

mq provides comparison functionality through built-in functions.

Basic Comparisons

Standard comparison operators are supported:

  • eq(a, b), a == b - Returns true if a equals b
  • ne(a, b), a != b - Returns true if a does not equal b
  • gt(a, b), a > b - Returns true if a is greater than b
  • gte(a, b), a >= b - Returns true if a is greater than or equal to b
  • lt(a, b), a < b - Returns true if a is less than b
  • lte(a, b), a <= b - Returns true if a is less than or equal to b

Examples

# Basic comparisons
1 == 1
# => true
2 > 1
# => true
"a" <= "b"
# => true

# String comparisons
"hello" == "hello"
# => true
"xyz" > "abc"
# => true

# Numeric comparisons
5.5 >= 5.0
# => true
-1 < 0
# => true

# Logical operations
and(true, false)
# => false
or(true, false)
# => true
not(false)
# => true

# Complex conditions
and(x > 0, x < 10)
# =>  true if 0 < x < 10

Regex Match Operator

The =~ operator tests whether a string matches a regular expression pattern. It returns true if the pattern matches and false otherwise.

Syntax

string =~ pattern

This is equivalent to calling is_regex_match(string, pattern).

Examples

# Basic regex match
"hello world" =~ "hello"
# => true

"hello world" =~ "^world"
# => false

# Match digits
"abc123" =~ "[0-9]+"
# => true

"abc" =~ "^[0-9]+$"
# => false

# Use in a conditional
"foo bar" | if (. =~ "foo"): "matched" else: "no match"
# => matched

# Complex patterns
"2024-01-15" =~ "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
# => true

try-catch

The try-catch expression allows you to handle errors gracefully by providing a fallback value when an expression fails.

Syntax

try <expr> catch <expr>

Behavior

  • If the try expression succeeds, its result is returned
  • If the try expression fails (produces an error), the catch expression is evaluated instead
  • The catch expression receives the same input as the try expression

Examples

Basic Error Handling

# When the expression succeeds
try: "value" catch: "unknown"

# When the expression fails
try: get("missing") catch: "default"

Chaining with Pipe

# Try to parse as JSON, fallback to raw string
try: from_json() catch: self

# Complex fallback logic
try: do get("data") | from_json(); catch: []

Nested Try-Catch

# Multiple fallback levels
try: get("primary") catch: try: get("secondary") catch: "default"

Error Suppression (?)

The error suppression operator ? provides a concise way to handle errors by returning None when an expression fails, instead of raising an error. This is equivalent to using a regular try-catch with a default fallback.

Examples

# Equivalent to a regular try-catch with a default value
get("missing")?

In this example, if get("missing") fails, the result will be None rather than an error.

Syntax

Syntax

This section outlines the syntax rules in mq, providing a clear reference for writing valid code.

Comments

Similar to jq, comments starting with # are doc-comments.

# doc-comment
let value = add(2, 3);

Control flow

If Expression

The if expression evaluates a condition and executes code based on the result:

 if (eq(x, 1)):
   "one"
 elif (eq(x, 2)):
   "two"
 else:
   "other"
 if (eq(x, 1)):
   do "one" | upcase();
 elif (eq(x, 2)):
   do "TWO" | downcase();
 else:
   do
    "other" | upcase()
   end
 if (eq(x, 1)):
   "one"

The if expression can be nested and chained with elif and else clauses. The conditions must evaluate to boolean values.

While Expression

The while loop repeatedly executes code while a condition is true:

let x = 5 |
while (x > 0):
  let x = x - 1 | x
end
# => 0

You can use break: <expr> to return a value from a while loop:

var x = 10 |
while (x > 0):
  x = x - 1 |
  if(eq(x, 3)):
    break: "Found three!"
  else:
    x
end
# => "Found three!"

Foreach Expression

The foreach loop iterates over elements in an array:

let items = array(1, 2, 3) |
foreach (x, items):
   sub(x, 1)
end
# => array(0, 1, 2)

You can use break: <expr> to exit early and return a specific value instead of an array:

let items = array(1, 2, 3, 4, 5) |
foreach (x, items):
  if(x > 3):
    break: "Found value greater than 3"
  else:
    x
end
# => "Found value greater than 3"

Foreach loops are useful for:

  • Processing arrays element by element
  • Mapping operations across collections
  • Filtering and transforming data

Loop Expression

The loop expression creates an infinite loop that continues until explicitly terminated with break:

var x = 0 |
loop:
  x = x + 1 |
  if(x > 5):
    break
  else:
    x
end
# => 5

The loop can be controlled using break to exit the loop and continue to skip to the next iteration:

var x = 0 |
loop:
  x = x + 1 |
  if(x < 3):
    continue
  elif(x > 5):
    break
  else:
    x
end
# => 5

The break statement can return a value from a loop using the break: <expr> syntax. This allows loops to be used as expressions that produce a specific value when exited:

var x = 0 |
loop:
  x = x + 1 |
  if(x > 5):
    break: "Found it!"
  else:
    x
end
# => "Found it!"

Loop expressions are useful for:

  • Implementing infinite loops with conditional exits
  • Creating retry mechanisms
  • Processing until a specific condition is met
  • Complex iteration patterns that don’t fit while or foreach

Pattern Matching

The match expression enables pattern matching on values, providing a powerful way to destructure and handle different data types.

Basic Syntax

match (value):
  | pattern: body
end

The match expression evaluates the value and compares it against a series of patterns. The first matching pattern’s body is executed.

Literal Patterns

Match against specific values:

match (x):
  | 1: "one"
  | 2: "two"
  | _: "other"
end

Literal patterns support:

  • Numbers: 1, 2.5, -10
  • Strings: "hello", "world"
  • Booleans: true, false
  • None: none

Type Patterns

Match based on value type using the :type_name syntax:

match (value):
  | :string: "is string"
  | :number: "is number"
  | :array: "is array"
  | :dict: "is dict"
  | :bool: "is boolean"
  | :none: "is none"
  | _: "other type"
end

Available type patterns:

  • :string - Matches string values
  • :number - Matches numeric values
  • :array - Matches array values
  • :dict - Matches dictionary values
  • :bool - Matches boolean values
  • :markdown - Matches markdown values
  • :none - Matches none value

Array Patterns

Destructure arrays and bind elements to variables:

match (arr):
  | []: "empty array"
  | [x]: x
  | [x, y]: add(x, y)
  | [first, second, third]: first
  | [first, ..rest]: first
end

Array patter features:

  • Match exact length: [x, y] matches arrays with exactly 2 elements
  • Rest pattern: ..rest captures remaining elements
  • Empty array: [] matches empty arrays
  • Variable binding: Elements are bound to named variables

Rest Pattern Example

match (arr):
  | [head, ..tail]: tail
end
# array(1, 2, 3, 4) => array(2, 3, 4)

Dict Patterns

Destructure dictionaries and extract values:

match (obj):
  | {name, age}: name
  | {x, y}: add(x, y)
  | {}: "empty dict"
  | _: "no match"
end

Dict pattern features:

  • Extract specific keys: {name, age} binds values to variables
  • Partial matching: Matches dicts that have at least the specified keys
  • Empty dict: {} matches empty dictionaries

Example with Object

let person = {"name": "Alice", "age": 30, "city": "Tokyo"} |
match (person):
  | {name, age}: s"${name} is ${age} years old"
  | {name}: name
  | _: "unknown"
end
# => "Alice is 30 years old"

Variable Binding

Bind the matched value to a variable:

match (value):
  | x: x + 1
end

Variable binding captures the entire value and makes it available in the body expression.

Wildcard Pattern

The underscore _ matches any value:

match (x):
  | 1: "one"
  | 2: "two"
  | _: "something else"
end

Use the wildcard pattern as the last arm to handle all remaining cases.

Guards

Add conditions to patterns using if:

match (n):
  | x if (x > 0): "positive"
  | x if (x < 0): "negative"
  | _: "zero"
end

Guards allow you to:

  • Add complex conditions to patterns
  • Filter matched values
  • Combine pattern matching with boolean logic

Guard Examples

# Match even numbers
match (n):
  | x if (x % 2 == 0): "even"
  | _: "odd"
end

# Match array with positive numbers
match (arr):
  | [x, ..] if (x > 0): "starts with positive"
  | _: "other"
end

Multiple Arms

Combine multiple patterns for comprehensive matching:

match (value):
  | 0: "zero"
  | x if (x > 0): "positive"
  | x if (x < 0): "negative"
  | :string: "text"
  | []: "empty array"
  | [x, ..rest]: "non-empty array"
  | {}: "empty dict"
  | _: "something else"
end

Pattern Matching vs If Expressions

Pattern matching provides several advantages over if expressions:

Using If Expressions

if (type_of(x) == "number"):
  if (x == 0):
    "positive number"
  elif (x < 0):
    "negative number"
  else:
    "zero"
elif (type_of(x) == "array"):
  if (len(x) == 0):
    "empty array"
  else:
    "non-empty array"
else:
  "other"

Using Pattern Matching

match (x):
  | n if (n > 0): "positive number"
  | n if (n < 0): "negative number"
  | 0: "zero"
  | []: "empty array"
  | [_, ..rest]: "non-empty array"
  | _: "other"
end

Practical Examples

Processing Different Data Types

def describe(value):
  match (value):
    | :none: "nothing"
    | :bool: "true or false"
    | x if (gt(x, 100)): "big number"
    | :number: "small number"
    | "": "empty string"
    | :string: "text"
    | []: "empty list"
    | [x]: s"list with one item: ${x}"
    | [_, ..rest]: "list with multiple items"
    | {}: "empty object"
    | _: "dictionary"
  end

Extracting Data from Structures

def get_first_name(user):
  match (user):
    | {name}: name
    | _: "unknown"
  end

Handling API Responses

def handle_response(response):
  match (response):
    | {status, data} if (eq(status, 200)): data
    | {status, error} if (eq(status, 404)): s"Not found: ${error}"
    | {status, error} if (eq(status, 500)): s"Server error: ${error}"
    | _: "Unknown response"
  end

Operator

Pipe Operator

A functional operator that allows chaining multiple filter operations together.

Usage

The pipe operator (|) enables sequential processing of filters, where the output of one filter becomes the input of the next filter.

Examples

# Basic pipe usage
42 | add(1) | mul(2)
# => 86

# Multiple transformations
let mul2 = def mul2(x): mul(x, 2);
let gt4 = def gt4(x): gt(x, 4);
array(1, 2, 3) | map(mul2) | filter(gt4)
# => [6]

# Function composition
let double = def _double(x): mul(x, 2);
let add_one = def _add_one(x): add(x, 1);
5 | double(self) | add_one(self)
# => 11

Shift Operators

The shift operators (<< and >>) perform different operations depending on the type of the operand.

Left Shift (<<)

The left shift operator (<<) maps to the shift_left(value, amount) builtin function.

Operand typeBehavior
NumberBitwise left shift: multiplies the value by 2^amount
StringRemoves amount characters from the start of the string
ArrayAppends the value to the end of the array
Markdown HeadingDecreases the heading depth by amount (promotes the heading, e.g. ###), minimum depth is 1

Examples

# Bitwise left shift on numbers
1 << 2
# => 4

shift_left(1, 3)
# => 8

# Remove characters from the start of a string
shift_left("hello", 2)
# => "llo"

"hello" << 2
# => "llo"

# Promote a heading (decrease depth)
let md = do to_markdown("## Heading 2") | first(); |
md << 1
# => # Heading 2

Right Shift (>>)

The right shift operator (>>) maps to the shift_right(value, amount) builtin function.

Operand typeBehavior
NumberBitwise right shift on the truncated integer value (shifts the bits right by amount)
StringRemoves amount characters from the end of the string
ArrayAdds the value to the beginning of the array
Markdown HeadingIncreases the heading depth by amount (demotes the heading, e.g. ###), maximum depth is 6

Examples

# Bitwise right shift on numbers
4 >> 2
# => 1

shift_right(8, 2)
# => 2

# Remove characters from the end of a string
shift_right("hello", 2)
# => "hel"

"hello" >> 2
# => "hel"

# Demote a heading (increase depth)
let md = do to_markdown("# Heading 1") | first(); |
md >> 1
# => ## Heading 1

Conversion Operator (@)

The conversion operator (@) converts a value to a different type or format. It maps to the convert(value, type) builtin function.

Usage

value @ type

The type operand can be a symbol or a string that specifies the target format. The supported conversion targets are:

Type (symbol)Type (string)Behavior
:h1"#"Convert to a Markdown heading level 1
:h2"##"Convert to a Markdown heading level 2
:h3"###"Convert to a Markdown heading level 3
:h4"####"Convert to a Markdown heading level 4
:h5"#####"Convert to a Markdown heading level 5
:h6"######"Convert to a Markdown heading level 6
:htmlConvert Markdown to an HTML string
:textExtract the plain text content of a node
:shShell-escape the value for safe use in shell commands
:base64Encode the value as a Base64 string
:uriURL-encode the value
:uridURL-decode the value
">"Convert to a Markdown blockquote
"-"Convert to a Markdown list item
"~~"Convert to a Markdown strikethrough
"<url>" (a valid URL string)Convert to a Markdown link with the given URL
"**"Convert to a Markdown strong/bold
"--"Convert to a Markdown horizontal rule

Examples

# Convert a string to a Markdown heading
"Hello World" @ :h1
# => # Hello World

"Hello World" @ :h2
# => ## Hello World

# Convert using string syntax
"Hello World" @ "##"
# => ## Hello World

# Convert to a blockquote
"Important note" @ ">"
# => > Important note

# Convert to a list item
"Item one" @ "-"
# => - Item one

# Convert to a strikethrough
"old text" @ "~~"
# => ~~old text~~

# Convert to a Markdown link
"mq" @ "https://harehare.github.io/mq"
# => [mq](https://harehare.github.io/mq)

# Convert Markdown to HTML
let md = do to_markdown("# Hello") | first(); |
md @ :html
# => "<h1>Hello</h1>"

# Extract plain text from a Markdown node
let md = do to_markdown("## Hello World") | first(); |
md @ :text
# => "Hello World"

# Shell-escape a string for safe use in shell
"hello world" @ :sh
# => 'hello world'

"safe-string" @ :sh
# => safe-string

# Encode to Base64
"hello" @ :base64
# => "aGVsbG8="

# URL-encode a string
"hello world" @ :uri
# => "hello%20world"

.. Operator

The range operator (..) creates sequences of consecutive values between a start and end point.

Usage

The range operator generates arrays of values from a starting point to an ending point (inclusive). It works with both numeric values and characters.

Examples

# Numeric ranges
1..5
# => [1, 2, 3, 4, 5]

# Character ranges
'a'..'e'
# => ["a", "b", "c", "d", "e"]

# Using ranges with other operations
1..3 | map(fn(x): mul(x, 2);)
# => [2, 4, 6]

# Reverse ranges
5..1
# => [5, 4, 3, 2, 1]

# Single element range
3..3
# => [3]

Assignment Operators

Assignment operators are used to assign values to variables and combine assignment with arithmetic or logical operations.

Simple Assignment

The basic assignment operator (=) assigns a value to a variable.

Usage

let x = 10 |
let name = "mq" |
let items = [1, 2, 3]

Update Operator (|=)

The update operator (|=) applies an expression to a selected value and updates it in place.

Usage

<selector.value> |= expr

The left side specifies what to update using a selector, and the right side is the expression that transforms the value.

Examples

# Update a code block
.code.value |= "test" | to_text()
# => test

# Update a header level
.h.depth |= 3 | .h.depth
# => 3

Compound Assignment Operators

Compound assignment operators combine an arithmetic or logical operation with assignment, providing a shorthand for updating variables.

Addition Assignment (+=)

Adds a value to a variable and assigns the result back to the variable.

var x = 10 |
x += 5
# => x is now 15

var count = 0 |
count += 1
# => count is now 1

Subtraction Assignment (-=)

Subtracts a value from a variable and assigns the result back to the variable.

var x = 10 |
x -= 3
# => x is now 7

var balance = 100 |
balance -= 25
# => balance is now 75

Multiplication Assignment (*=)

Multiplies a variable by a value and assigns the result back to the variable.

var x = 5 |
x *= 3
# => x is now 15

var price = 100 |
price *= 1.1
# => price is now 110

Division Assignment (/=)

Divides a variable by a value and assigns the result back to the variable.

var x = 20 |
x /= 4
# => x is now 5

var total = 100 |
total /= 2
# => total is now 50

Modulo Assignment (%=)

Computes the remainder of dividing a variable by a value and assigns the result back to the variable.

var x = 17 |
x %= 5
# => x is now 2

var count = 23 |
count %= 10
# => count is now 3

Floor Division Assignment (//=)

Divides a variable by a value, floors the result (rounds down to the nearest integer), and assigns it back to the variable.

var x = 17 |
x //= 5
# => x is now 3

var count = 23 |
count //= 10
# => count is now 2

Def Expression

The def expression defines reusable functions with parameters:

Syntax

def function_name(parameters):
  program;

Examples

# Function that doubles input
def double(x):
  mul(x, 2);

# Function with conditional logic
def is_positive(x):
  gt(x, 0);

# Composition of functions
def add_then_double(x, y):
  add(x, y) | double(self);

Default Parameters

You can define default values for function parameters. Parameters with default values can be omitted when calling the function.

Syntax

def function_name(param1, param2=default_value):
  program;

Examples

# Function with default parameter
def greet(name, greeting="Hello"):
  greeting + " " + name;

# Using default value
greet("Alice")
# Output: "Hello Alice"

# Overriding default value
greet("Bob", "Hi")
# Output: "Hi Bob"

# Default value can be an expression
def add_with_offset(x, offset=10 + 5):
  x + offset;

add_with_offset(20)
# Output: 35

Rules

  • Parameters with default values must come after parameters without default values
  • Default values are evaluated when the function is called, not when it’s defined
  • Default values can be any valid expression

Environment variables

Environment variables can be referenced using $XXX syntax, where XXX represents the name of the environment variable. For example:

  • $PATH - References the PATH environment variable
  • $HOME - References the HOME environment variable
  • $USER - References the current user’s username

This syntax is commonly used in shell scripts and configuration files to access system-level environment variables.

Color Configuration

NO_COLOR

When set to a non-empty value, disables all colored output regardless of the -C flag. This follows the NO_COLOR standard.

# Disable colored output
NO_COLOR=1 mq -C '.h' README.md

MQ_COLORS

Customizes the colors used when -C (color output) is enabled. The format is a colon-separated list of key=value pairs, where each value is a semicolon-separated list of SGR (Select Graphic Rendition) parameters.

# Make headings bold red, code blocks blue
export MQ_COLORS="heading=1;31:code=34"
mq -C '.h' README.md

Only the specified keys are overridden; unspecified keys use the default colors. Invalid entries are silently ignored.

Available Keys

KeyDescriptionDefault
headingHeadings (#, ##, etc.)bold cyan (1;36)
codeFenced code blocksgreen (32)
code_inlineInline codegreen (32)
emphasisItalic text (*text*)italic yellow (3;33)
strongBold text (**text**)bold (1)
linkLinks ([text](url))underline blue (4;34)
link_urlLink URLsblue (34)
imageImages (![alt](url))magenta (35)
blockquoteBlockquote markers (>)dim (2)
deleteStrikethrough (~~text~~)red dim (31;2)
hrHorizontal rules (---)dim (2)
htmlInline HTMLdim (2)
frontmatterYAML/TOML frontmatterdim (2)
listList markers (-, *, 1.)yellow (33)
tableTable separatorsdim (2)
mathMath expressions ($...$)green (32)

Common SGR Codes

CodeEffect
0Reset
1Bold
2Dim
3Italic
4Underline
31Red
32Green
33Yellow
34Blue
35Magenta
36Cyan
37White

Fn Expression

Anonymous functions (lambda expressions) allow you to define functions inline without naming them. These functions can be passed as arguments to other functions, assigned to variables, or used directly in expressions.

Syntax

fn(parameters): program;

Examples

# Basic Anonymous Function
nodes | map(fn(x): add(x, "1");)

# Using Anonymous Functions as Callbacks
nodes | .[] | sort_by(fn(x): to_text(x);)

Default Parameters

Anonymous functions also support default parameter values, just like named functions defined with def.

Syntax

fn(param1, param2=default_value): program;

Examples

# Anonymous function with default parameter
let multiply = fn(x, factor=2): x * factor;

# Using default value
multiply(10)
# Multiplies each value by 2 (default factor)

# Overriding default value
multiply(10, 3)
# Multiplies each value by 10

# Using in callbacks
[1, 2] | map(fn(x, prefix="Item: "): prefix + to_text(x);)

Rules

  • Parameters with default values must come after parameters without default values
  • Default values are evaluated when the function is called
  • Default values can be any valid expression

Macros

Macros enable compile-time code generation and transformation in mq. They allow you to define reusable code templates that are expanded before evaluation.

Syntax

macro name(parameters): body

Macros are invoked like functions:

name(arguments)

How Macros Work

Macros differ from functions:

  • Compile-time expansion: Macros are expanded before the program executes
  • Code substitution: Macro parameters are directly substituted into the macro body
  • No runtime overhead: Macro definitions are removed from the final program

Basic Examples

# Simple value transformation
macro double(x) do
  x + x
end

| double(5)  # Returns 10

# Multiple parameters
macro add_three(a, b, c) do
  a + b + c
end

| add_three(1, 2, 3)  # Returns 6

# With control flow
macro max(a, b) do
  if(a > b): a else: b
end

| max(10, 5)  # Returns 10

Advanced Examples

# Nested macro calls
macro double(x): x + x
macro quadruple(x): double(double(x))

| quadruple(3)  # Returns 12

# Accepting functions as parameters
macro apply_twice(f, x) do
  f(f(x))
end

def inc(n): n + 1;
| apply_twice(inc, 5)  # Returns 7

Quote and Unquote

quote and unquote provide advanced metaprogramming capabilities:

  • quote(expr): Delays evaluation, treating content as code to be generated
  • unquote(expr): Evaluates the expression immediately and injects the result

Practical Examples

# Basic injection
macro make_expr(x) do
  quote: unquote(x) + 1
end

| make_expr(5)  # Returns 6

# Pre-computation
macro compute(a, b) do
  quote: unquote(a) + unquote(b) * 2
end

| compute(10, 5)  # Returns 20

# Conditional code generation
macro conditional_expr(x) do
  quote do
    if(unquote(x) > 10):
        "large"
    else:
        "small"
  end
end

| conditional_expr(15)  # Returns "large"

# Complex pre-computation
macro compute_mixed(x) do
  let a = x * 2 |
  let b = x + 10 |
  quote: unquote(a) + unquote(b)
end

| compute_mixed(5)  # a=10, b=15, returns 25

# Generating data structures
macro make_array(a, b, c) do
  quote: [unquote(a), unquote(b), unquote(c)]
end

| make_array(1, 2, 3)  # Returns [1, 2, 3]

Modules and Imports

mq provides several ways to organize and reuse code: module, import, and include.

Module

Defines a module to group related functions and prevent naming conflicts using the syntax module name: ... end.

module module_name:
  def function1(): ...
  def function2(): ...
end

Functions within a module can be accessed using qualified access syntax:

module_name::function1()

Examples

# Define a math module
module math:
  def add(a, b): a + b;
  def sub(a, b): a - b;
  def mul(a, b): a * b;
end

# Use functions from the module
| math::add(5, 3)  # Returns 8
| math::mul(4, 2)  # Returns 8

Import

Loads a module from an external file using the syntax import "module_path". The imported module is available with its defined name and can be accessed using qualified access syntax.

The import directive searches for .mq files in the following locations:

  • $HOME/.mq - User’s home directory mq folder
  • $ORIGIN/../lib/mq - Library directory relative to the source file
  • $ORIGIN/../lib - Parent lib directory relative to the source file
  • $ORIGIN - Current directory relative to the source file
import "module_name"

Examples

math.mq:

def add(a, b): a + b;
def sub(a, b): a - b;

main.mq:

# Import the math module
import "math"

# Use functions with qualified access
| math::add(10, 5)  # Returns 15
| math::sub(10, 5)  # Returns 5

Include

Loads functions from an external file directly into the current namespace using the syntax include "module_name". Unlike import, functions are available without a namespace prefix.

The include directive searches for .mq files in the same locations as import.

include "module_name"

Examples

math.mq:

def add(a, b): a + b;
def sub(a, b): a - b;

main.mq:

# Include math functions
include "math"

# Functions are available directly
| add(2, 3)  # Returns 5
| sub(10, 4) # Returns 6

Comparison

Featuremoduleimportinclude
PurposeDefine a moduleLoad external moduleLoad external functions
AccessQualified access (module::func)Qualified access (module::func)Direct access (func)
Use caseOrganize code within a fileReuse modules across filesSimple function sharing

Variable Declarations

Let

The let binds an immutable value to an identifier for later use:

# Binds 42 to x
let x = 42
# Uses x in an expression
| let y = x + 1
# Binds `add` function to z
| let z = do let z = fn(x): x + 1; | z(1);

Once a variable is declared with let, its value cannot be changed.

Var

The var declares a mutable variable that can be reassigned:

# Declares a mutable variable
var counter = 0
# Reassigns the value
counter = counter + 1
# counter is now 1

Variables declared with var can be modified using the assignment operator (=):

var total = 100
| total = total - 25
# total is now 75

var message = "Hello"
| message = message + " World"
# message is now "Hello World"

Choosing Between Let and Var

  • Use let when you want to create an immutable binding (most cases)
  • Use var when you need to modify the value after declaration (counters, accumulators, etc.)

Self

The current value being processed can be referenced as self or . (dot). Both self and . behave identically. When there are insufficient arguments provided in a method call, the current value (self) is automatically passed as the first argument.

Examples

# These expressions are equivalent
"hello" | upcase()
"hello" | upcase(self)
"hello" | upcase(.)

String Interpolation

String Interpolation allow embedding expressions directly inside string literals. In mq, an interpolated string is prefixed with s" and variables can be embedded using ${} syntax.

Syntax

s"text ${ident} more text"

Escaping

You can escape the $ character in a string interpolation by using $$. This allows you to include literal $ symbols in your interpolated strings.

let price = 25
| s"The price is $$${price}"
# => Output: "The price is $25"

Examples

let name = "Alice"
| let age = 30
| s"Hello, my name is ${name} and I am ${age} years old."
# => Output: "Hello, my name is Alice and I am 30 years old."

Nodes

The nodes in mq allows you to access and manipulate all Markdown nodes as a single flat array.

Basic Usage

The nodes filter returns an array of all nodes in a Markdown document:

nodes

Examples

Finding all headings

nodes | select(.h)

Converting all text to uppercase

nodes | map(upcase)

Counting nodes by type

nodes | len()

Builtin selectors and functions

mq - Function Reference