Syntax

A Loom document is Markdown with a few marks added. Everything Markdown already does still works — headings, emphasis, lists, tables, links — and the marks say what Loom should do with the text around them.

There are eight of them, and each does a single thing. Learn these and you can read any corpus.

The marks

mark what it does
--- fences the frontmatter that opens a chapter
# names a section, and gathers the sections beneath it when it sits higher up
[file.ts] names the file a section's code belongs to
{Tangle} labels a heading — what Loom should make of it, or what language it holds
=> opens a code block
~> opens a code block that only ~ closes
~ closes a code block and returns to prose
::[a section] draws that section in, whole
{{ name }} binds a value into the text

Two more constructs look like marks but are the opposite of one, because each names a stretch of text Loom refuses to read. Three backticks fence a region left entirely alone, which is how this page quotes marks without invoking them. A :::[Note] block holds a remark for whoever opens the corpus next, and tangling drops it.

Frontmatter

A chapter opens on a --- fence. Inside it, a few fields say where the chapter sits and what it is made of.

---
Part IV: The format
Chapter 1: Syntax
Target: src/
Language: TypeScript
---

Part and Chapter place the chapter in the corpus, and they are what the table of contents and the navigation are built from. Target names the directory the chapter's files are written into. Language declares what the chapter's code is written in, and Prose is a language like any other — a chapter that declares it holds prose and nothing else, which is what these documentation pages are.

Sections

A # heading opens a section, and everything under it belongs to that section until the next heading.

A section's title is also its name. That name is how every other part of the corpus refers to it, so titles are not decoration — renaming a section renames the thing that draws it in.

Parts

A part is the same mark one level deeper. Where a # heading names a section, a ## heading beneath it gathers the sections that follow, and a chapter divides into stages a reader can follow without dividing the file those stages tangle into.

Depth is the only difference, so there is no separate mark to learn — a heading is a heading, and where you put it decides whether it names a thing or groups them.

Naming a file

A heading may name the file its code belongs to, by putting the path in brackets:

## The parser [parser.ts]

Everything that section composes is written to parser.ts, under the chapter's Target directory. One chapter can fill several files, and several chapters can fill one.

Labelling a heading

A heading may also carry a label in braces, which says what Loom should make of the section rather than where its code goes.

## The module {Tangle}[parser.ts]
# Table of Contents {TOC}
## Installing it {Bash}

{Tangle} marks the section that composes a file, and it is the long form of the bracket on its own. {TOC} marks the heading whose list of parts and chapters Loom keeps up to date. A language in braces switches that one section into another language, so a chapter of TypeScript can drop into a shell command or a block of JSON without leaving the document.

Opening and closing code

The => operator opens a code block. The ~ separator closes it and returns to prose. Between them the text is code in the chapter's language, and Loom treats it as opaque — it reads the marks, never the language.

The parser reads one line at a time.
=>
export const parse = (line: string) => line.trim()
~
Trimming first means the caller never has to.

Prose and code alternate as often as the explanation needs. A block is not a unit of compilation; it is a unit of narrative.

A block opened with => also ends at the next heading. That suits most code, but some files are full of lines that look like headings: a Markdown page, a shell script, a configuration file with # comments. Open those with ~> instead. The block then holds until ~, and every # line inside it stays part of the file.

## The skill [SKILL.md]
~>
# Writing for the reader
## Put the actor in the subject
~

The tilde that closes a block stands alone at the start of its line, or with a space and some prose after it. A line that merely begins with a tilde, such as ~/bin in a script, stays code. An explicit block that never closes is an error, because it would otherwise swallow the rest of the chapter.

Anchors

An anchor draws another section in by name, and it is how a file gets assembled out of the pieces that explain it:

## The module [parser.ts]
=>
::[The tokens]
::[The parser]

Order on the page and order in the file are separate concerns. A section may be explained early and composed late, or the reverse, because the anchor decides where its code lands.

Tangling checks every anchor. Name a section that does not exist and the build stops and says so, which is what keeps a document and its files from drifting apart as the document is edited.

Inside a code block an anchor is always an anchor. To write one as an example rather than as a reference — as this page does throughout — put it in backticks or inside a fence, and Loom leaves it as text.

Warps

A warp binds a value into the text, written {{ name }}. Where a section repeats a version number, a package name or a path, the warp names it once and the corpus reads it everywhere.

Tangling

loom tangle walks the corpus, resolves every anchor and warp, and writes each file out. What it produces is ordinary source: no marker, no runtime, nothing of Loom left in it. A reader of the output cannot tell it came from a document.

loom weave renders the same corpus the other way, as a document to read. These pages are the result.

Loom