The container nodes
LoomAst defines the container nodes — the tree the parse pipeline builds from a file's wefts. A LoomDocument holds one LoomSection per heading, and each section holds a LoomHeading plus its body wefts. The tree is flat: heading level is for the reader, never nesting.
The heading
A LoomHeading is a section's heading — the same parts the HeadingWeft carried: the start marker, an optional title, an optional language specifier, and an optional sink.
export const LoomHeadingSchema = loomNode('LoomHeading', {
headingStart: HeadingStartTokenSchema,
title: Schema.optional(HeadingTitleTokenSchema),
specifier: Schema.optional(SpecifierTokenSchema),
sink: Schema.optional(SinkTokenSchema),
})
export type LoomHeading = typeof LoomHeadingSchema.TypeThe section
A LoomSection is one structural unit, opened by a heading at any level. Its body is two ordered runs of wefts: the preamble before the first transition, and the code after — the => and ~ chunks in source order. A {TOC} section carries a third run, its entries — the toc wefts that list the book's parts and chapters. An ordinary section has none.
export const LoomSectionSchema = loomNode('LoomSection', {
heading: LoomHeadingSchema,
preamble: Schema.Array(PreambleWeftSchema),
code: Schema.Array(SectionBodyWeftSchema),
entries: Schema.optional(Schema.Array(TocWeftSchema)),
})
export type LoomSection = typeof LoomSectionSchema.TypeThe frontmatter
A LoomFrontmatter is a chapter's opening block, parsed. It carries the membership line's part, partName, chapter, and title, and the block's fields: the package a {Tangle} section writes to, the language its sections inherit, and the status and priority a roadmap reads. A numbered part fills part; a numberless one fills partName alone. Every slot is optional, so a partial block still parses to a node the diagnostics can fault, and a file with no block has no frontmatter at all.
export const LoomFrontmatterSchema = loomNode('LoomFrontmatter', {
part: Schema.optional(FrontmatterPartTokenSchema),
partName: Schema.optional(FrontmatterPartNameTokenSchema),
chapter: Schema.optional(FrontmatterChapterTokenSchema),
title: Schema.optional(FrontmatterTitleTokenSchema),
package: Schema.optional(FrontmatterValueTokenSchema),
language: Schema.optional(FrontmatterValueTokenSchema),
status: Schema.optional(FrontmatterValueTokenSchema),
priority: Schema.optional(FrontmatterValueTokenSchema),
})
export type LoomFrontmatter = typeof LoomFrontmatterSchema.TypeThe document
A LoomDocument is the root, the implicit module named by its file. Its frontmatter is the opening block, when the file has one; its preamble is the run of wefts between that block and the first heading — carrying any value Warps; and its sections are the flat list that follows. Every slot may be empty.
export const LoomDocumentSchema = loomNode('LoomDocument', {
frontmatter: Schema.optional(LoomFrontmatterSchema),
preamble: Schema.Array(PreambleWeftSchema),
sections: Schema.Array(LoomSectionSchema),
})
export type LoomDocument = typeof LoomDocumentSchema.TypeWhat this module draws on
Each container is a loomNode built from Effect's Schema. The heading's subtokens come from LoomTokens; the preamble and body weft types come from Weft.
import { Schema } from 'effect'
import { loomNode } from '#ast/LoomNode'
import {
FrontmatterChapterTokenSchema,
FrontmatterPartTokenSchema,
FrontmatterPartNameTokenSchema,
FrontmatterTitleTokenSchema,
FrontmatterValueTokenSchema,
HeadingStartTokenSchema,
HeadingTitleTokenSchema,
SinkTokenSchema,
SpecifierTokenSchema,
} from './LoomTokens'
import { PreambleWeftSchema, SectionBodyWeftSchema, TocWeftSchema } from './Weft'