Files

92 lines
3.8 KiB
Markdown

# Architecture
How scriptorium is put together. Every node, package and arrow below exists in the
source tree; nothing is aspirational.
## Overview
```mermaid
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
```mermaid
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.