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