Architecture
Pipeline
Ascribe processes AsciiDoc through a linear pipeline:
Source (String / Files)
|
v
Parser (Parsley combinators)
|
v
AST (io.eleven19.ascribe.ast)
|
v
Rewrite Rules (optional transforms)
|
v
Bridge (AstToAsg.convert)
|
v
ASG (io.eleven19.ascribe.asg)
|
v
Renderers (AsciiDoc, JSON, custom)
|
v
Sink (String / Files)
Each stage is a pure function or a Kyo effect. The parser produces an AST with source positions, rewrite rules can transform the AST, the bridge converts it to the ASG model matching the official AsciiDoc schema, and renderers produce output in various formats.
Module Structure
Mill modules live under the ascribe/ directory (nested names use dots: ascribe/core/ → ascribe.core, ascribe/pipeline/kyo/ → ascribe.pipeline.kyo).
| Module | Mill | Artifact | Package | Purpose |
|---|---|---|---|---|
| Core | ascribe.core |
ascribe-core |
io.eleven19.ascribe |
Parser, lexer, AST types, AST visitor, DSL |
| ASG | ascribe.asg |
ascribe-asg |
io.eleven19.ascribe.asg |
ASG node types, JSON codecs, ASG visitor, DSL |
| Bridge | ascribe.bridge |
ascribe-bridge |
io.eleven19.ascribe.bridge |
AST-to-ASG converter |
| Pipeline (core) | ascribe.pipeline.core |
ascribe-pipeline-core |
io.eleven19.ascribe.pipeline.core |
PipelineOp, RewriteRule, pure rewrites |
| Pipeline (Kyo) | ascribe.pipeline.kyo |
ascribe-pipeline-kyo |
io.eleven19.ascribe.pipeline |
Kyo-backed pipeline, file I/O, includes |
| Pipeline (HTML) | ascribe.pipeline.html |
ascribe-pipeline-html |
io.eleven19.ascribe.pipeline.html |
scalatags HTML output |
| Pipeline (Markdown) | ascribe.pipeline.markdown |
ascribe-pipeline-markdown |
io.eleven19.ascribe.pipeline.markdown |
GFM via zio-blocks-docs |
| Pipeline (Ox) | ascribe.pipeline.ox |
ascribe-pipeline-ox |
io.eleven19.ascribe.pipeline.ox |
Ox-backed runtime (optional) |
| TCK Runner | ascribe.tck-runner |
— | build.ascribe.tckrunner |
TCK test harness (Cucumber) — not published |
AST Type Hierarchy
The parser produces an AST rooted at AstNode:
AstNode
+-- Document (header: Option[DocumentHeader], blocks: List[Block])
+-- DocumentHeader (title: InlineContent, attributes: List[(String, String)])
+-- Block
| +-- Heading (level, title)
| +-- Section (level, title, blocks)
| +-- Paragraph (content: InlineContent)
| +-- Listing (delimiter, content: String, attributes, title)
| +-- Literal (delimiter, content: String, attributes, title)
| +-- Sidebar (delimiter, blocks, attributes, title)
| +-- Example (delimiter, blocks, attributes, title)
| +-- Quote (delimiter, blocks, attributes, title)
| +-- Open (delimiter, blocks, attributes, title)
| +-- Pass (delimiter, content: String, attributes, title)
| +-- Comment (delimiter, content: String)
| +-- UnorderedList (items: List[ListItem])
| +-- OrderedList (items: List[ListItem])
| +-- Table (rows, delimiter, format, attributes, title, hasBlankAfterFirstRow)
+-- TableRow (cells: List[TableCell])
+-- TableCell (content: CellContent) -- CellContent = Inlines | Blocks
+-- AttributeList, BlockTitle, TableFormat
+-- Inline
| +-- Text (content: String)
| +-- Bold (content: List[Inline])
| +-- ConstrainedBold (content: List[Inline])
| +-- Italic (content: List[Inline])
| +-- Mono (content: List[Inline])
+-- ListItem (content: InlineContent)
Delimited blocks (Listing, Literal, Sidebar, Example, Quote, Open, Pass) support variable-length fences and nesting. Content-only blocks (Listing, Literal, Pass, Comment) capture their body as a raw string; container blocks (Sidebar, Example, Quote, Open) parse their body as nested blocks.
All AST nodes carry a Span (start/end source positions).
ASG Type Hierarchy
The ASG is a richer model matching the AsciiDoc specification schema:
Node (sealed trait)
+-- Document (attributes, header, blocks)
+-- Block (sealed trait)
| +-- Section, Heading, Paragraph, Listing, Literal, Pass, Stem, Verse
| +-- Sidebar, Example, Admonition, Open, Quote
| +-- List, DList, ListItem, DListItem
| +-- Table (cols, rows), TableRow (cells), TableCell (style, colspan, rowspan, content)
| +-- ColumnSpec, CellStyle, ColSpan, RowSpan, DupCount
| +-- Break, Audio, Video, Image, Toc
+-- Inline (sealed trait)
+-- Span (variant, form, inlines) -- formatting spans
+-- Ref (variant, target, inlines) -- links/xrefs
+-- Text (value) -- plain text
+-- CharRef (value) -- character references
+-- Raw (value) -- passthrough content
All ASG nodes carry a Location (start/end Position with 1-based line and column).
Key Design Decisions
- Sealed traits: Both AST and ASG hierarchies use sealed traits, enabling exhaustive pattern matching and safe
derives Schemaderivation. - Schema.derived codecs: ASG JSON serialization uses
zio-blocks-schemawithDiscriminatorKind.Field("name")to produce TCK-compatible JSON without hand-written codecs. - Private constructors + smart apply: ASG case classes have
privateconstructors. Companionapplymethods set thenodeTypefield automatically (always"block","inline", or"string"), preventing invalid states. - Position tracking via ParserBridges: The parser uses custom
PosParserBridgetraits that capture Parsley source positions and thread them into AST node constructors. - Section restructuring: The parser emits flat headings;
DocumentParser.restructuregroups them into nestedSectiontrees based on heading level. - Location as array: The ASG
Locationtype serializes as a JSON array[[startLine, startCol], [endLine, endCol]]viaSchema.transform, matching the TCK schema. - Kyo effects in pipeline: The pipeline module uses Kyo's effect system for composable I/O, allowing pipelines to be constructed without committing to a specific execution strategy.
- Shared build traits: Compiler settings (including
-Werror) are centralized inCommonScalaModuleandCommonScalaTestModulemeta-build traits.