SourceThe shape of a loomThe container nodes

Part II, Chapter 5

Part II, Chapter 5: The container nodes

Package: packages/loom-ast/src/ast/LoomAst.ts

Language: TypeScript

Raw source

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.

TypeScript
export const LoomHeadingSchema = loomNode('LoomHeading', {
  headingStart: HeadingStartTokenSchema,
  title: Schema.optional(HeadingTitleTokenSchema),
  specifier: Schema.optional(SpecifierTokenSchema),
  sink: Schema.optional(SinkTokenSchema),
})
export type LoomHeading = typeof LoomHeadingSchema.Type

The 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.

TypeScript
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.Type

The 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.

TypeScript
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.Type

The 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.

TypeScript
export const LoomDocumentSchema = loomNode('LoomDocument', {
  frontmatter: Schema.optional(LoomFrontmatterSchema),
  preamble: Schema.Array(PreambleWeftSchema),
  sections: Schema.Array(LoomSectionSchema),
})
export type LoomDocument = typeof LoomDocumentSchema.Type

What 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.

TypeScript
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'
Loom