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
- Create the bat syntax directory if it doesn’t exist:
mkdir -p "$(bat --config-dir)/syntaxes"
- 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
- 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:
- Visual Studio Code: Install the mq extension
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
| Key | Action |
|---|---|
: (colon) | Enter query mode |
Enter | Execute query |
Esc / q | Exit query mode / Exit app |
↑/k, ↓/j | Navigate results |
d | Toggle detail view |
? / F1 | Show help screen |
Ctrl+l | Clear query |
PgUp/PgDn | Page through results |
Home/End | Jump 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 queriesextract_markdown- Extracts content from Markdown using mq queriesavailable_functions- Lists available mq functionsavailable_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 headingsselect(.code.lang == "js")- Select JavaScript code blocks.text- Extract all text contentselect(.h1, .h2)- Select h1 and h2 headingsselect(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:
Navigation Commands
| Command | Alias | Description |
|---|---|---|
step | s | Step into the next expression, diving into function calls |
next | n | Step over the current expression, skipping over function calls |
finish | f | Run until the current function returns |
continue | c | Continue normal execution until the next breakpoint |
Breakpoint Commands
| Command | Alias | Description |
|---|---|---|
breakpoint [line] | b [line] | Set a breakpoint at the specified line number |
breakpoint | b | List all active breakpoints |
clear [id] | cl [id] | Clear a specific breakpoint by ID |
clear | cl | Clear all breakpoints |
Inspection Commands
| Command | Alias | Description |
|---|---|---|
info | i | Display current environment variables and context |
list | l | Show source code around the current execution point |
long-list | ll | Show the entire source code with line numbers |
backtrace | bt | Print the current call stack |
Control Commands
| Command | Alias | Description |
|---|---|---|
help | - | Display help information for all commands |
quit | q | Quit 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:
~/.mq/bin/directory- 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"]
Link and MDX Operations
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.
Extract URLs from Links
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
| Type | Description | Examples |
|---|---|---|
| Number | Represents numeric values. | 1, 3.14, -42 |
| String | Represents sequences of characters, including Unicode code points and escape sequences in the form of \{0x000}. | "hello", "123", "😊", "\u{1F600}" |
| Symbol | Represents immutable, interned identifiers prefixed with :. Used for constant values and keys. | :value, :success, :error, :ok |
| Boolean | Represents truth values. | true, false |
| Array | Represents ordered collections of values. | [1, 2, 3], array(1, 2, 3) |
| Dict | Represents key-value mappings (dictionaries). | {"a": 1, "b": 2}, dict(["a", 1], ["b", 2]) |
| Function | Represents 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
startorendis 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 bothaandbare trueor(a, b), a || b- Returns true if eitheraorbis truenot(a), !a- Returns true ifais 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 ifaequalsbne(a, b), a != b- Returns true ifadoes not equalbgt(a, b), a > b- Returns true ifais greater thanbgte(a, b), a >= b- Returns true ifais greater than or equal toblt(a, b), a < b- Returns true ifais less thanblte(a, b), a <= b- Returns true ifais less than or equal tob
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
tryexpression succeeds, its result is returned - If the
tryexpression fails (produces an error), thecatchexpression is evaluated instead - The
catchexpression receives the same input as thetryexpression
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:
..restcaptures 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 type | Behavior |
|---|---|
| Number | Bitwise left shift: multiplies the value by 2^amount |
| String | Removes amount characters from the start of the string |
| Array | Appends the value to the end of the array |
| Markdown Heading | Decreases 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 type | Behavior |
|---|---|
| Number | Bitwise right shift on the truncated integer value (shifts the bits right by amount) |
| String | Removes amount characters from the end of the string |
| Array | Adds the value to the beginning of the array |
| Markdown Heading | Increases 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 |
:html | Convert Markdown to an HTML string | |
:text | Extract the plain text content of a node | |
:sh | Shell-escape the value for safe use in shell commands | |
:base64 | Encode the value as a Base64 string | |
:uri | URL-encode the value | |
:urid | URL-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
| Key | Description | Default |
|---|---|---|
heading | Headings (#, ##, etc.) | bold cyan (1;36) |
code | Fenced code blocks | green (32) |
code_inline | Inline code | green (32) |
emphasis | Italic text (*text*) | italic yellow (3;33) |
strong | Bold text (**text**) | bold (1) |
link | Links ([text](url)) | underline blue (4;34) |
link_url | Link URLs | blue (34) |
image | Images () | magenta (35) |
blockquote | Blockquote markers (>) | dim (2) |
delete | Strikethrough (~~text~~) | red dim (31;2) |
hr | Horizontal rules (---) | dim (2) |
html | Inline HTML | dim (2) |
frontmatter | YAML/TOML frontmatter | dim (2) |
list | List markers (-, *, 1.) | yellow (33) |
table | Table separators | dim (2) |
math | Math expressions ($...$) | green (32) |
Common SGR Codes
| Code | Effect |
|---|---|
0 | Reset |
1 | Bold |
2 | Dim |
3 | Italic |
4 | Underline |
31 | Red |
32 | Green |
33 | Yellow |
34 | Blue |
35 | Magenta |
36 | Cyan |
37 | White |
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 generatedunquote(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
| Feature | module | import | include |
|---|---|---|---|
| Purpose | Define a module | Load external module | Load external functions |
| Access | Qualified access (module::func) | Qualified access (module::func) | Direct access (func) |
| Use case | Organize code within a file | Reuse modules across files | Simple 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
letwhen you want to create an immutable binding (most cases) - Use
varwhen 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()