Files

3.8 KiB

Architecture

How scriptorium is put together. Every node, package and arrow below exists in the source tree; nothing is aspirational.

Overview

flowchart TD
    Caller[consumer] --> Root[scriptorium, public surface]
    Root --> Markdown[internal/markdown]
    Root --> Math[internal/mathml]
    Root --> Diagram[internal/diagram]
    Markdown --> Block[parse.go, block parser]
    Markdown --> Inline[inline.go, inline parser]
    Markdown --> Writer[render.go, HTML writer]
    Math --> Tok[token.go, tokeniser]
    Math --> MParse[parse.go and command.go, parser]
    Math --> Tables[symbols.go, symbol tables]
    Math --> Env[environments.go, tables]
    Math --> Macros[macros.go, expansion]
    Diagram --> DParse[flowparse.go and sequence.go, parsers]
    Diagram --> DLayout[flowlayout.go and sequence.go, layout]
    Diagram --> DWrite[svg.go, writer]

The root package is the whole public surface: Render hands Markdown source to the Markdown engine, RenderMath and RenderMathDisplay hand TeX source to the mathematics engine, RenderDiagram hands Mermaid source to the diagram engine, and SupportedDiagram states the Mermaid scope. Everything else lives in internal packages and reaches no caller.

The Markdown engine parses line by line into the block tree CommonMark defines; RenderHTML walks the tree, parses the inline content of every leaf against the document's link reference definitions and footnotes, and writes HTML.

The mathematics engine tokenises the TeX source, expands the bounded macros, parses atoms with their scripts into a MathML tree and writes one line of XML. A construct the tables do not carry degrades in place to its verbatim source inside an merror element.

The diagram engine parses statement lines into flowchart or sequence models, lays the flowchart out by rank and barycentre and the sequence along its lifelines, and writes SVG. A diagram type outside the two grammars is refused with an error naming it.

Packages

Package Responsibility
scriptorium (root) the public surface: Render, RenderMath, RenderMathDisplay, RenderDiagram, SupportedDiagram. Owns nothing else.
internal/markdown the Markdown engine: block parsing, inline parsing, HTML writing, and the scanners both phases share. Carries no state between calls.
internal/mathml the mathematics engine: tokenising, macro expansion, symbol tables, environments, accents, styles and the XML writer. Carries no state between calls.
internal/diagram the diagram engine: the flowchart and sequence parsers, both layouts and the SVG writer. Carries no state between calls.

Data flow

sequenceDiagram
    participant Caller
    participant S as scriptorium.RenderMath
    participant T as mathml tokeniser
    participant P as mathml parser
    participant W as mathml writer
    Caller->>S: TeX source
    S->>T: source
    T-->>P: tokens
    P->>P: expand macros, parse atoms and scripts
    P-->>W: MathML tree
    W-->>Caller: one math element

Rendering cannot fail. A construct outside the grammar stays in the output as its literal source rather than becoming an error, and nothing is sanitised on the way out: that is the consumer's policy.

Parsing and rendering are pure: no globals, no caches, no goroutines. The same source produces byte-identical output on every call, which the tests assert.

State and lifetime

  • Everything is per-call: the block tree, the delimiter stack, the reference map, the token list, the macro table, the diagram models and the layouts die with the render.
  • All functions are safe for concurrent use, because nothing is shared.

Dependencies

None beyond the standard library. That is a contract of the project, not an accident: every engine writes its own scanners and tables where the standard library has nothing to offer.