# Codebase Patterns: Wikitext Parser Reference for the key architecture, data flow, and internal patterns. Read this before making any non-trivial change to core modules. ## Pipeline architecture Events, not AST, are the fundamental output. Everything else is a consumer. The pipeline accepts any `TextSource` (plain `string` satisfies the interface). ``` TextSource ─► Tokenizer ─► Block Parser ─► Inline Parser ─► [Consumer] │ │ │ │ │ charCodeAt │ line-start │ recursive ├─► buildTree() │ scanner │ dispatch │ descent ├─► compileHtml() │ │ │ with stack ├─► filterEvents() │ │ │ └─► direct callback │ │ │ │ │ └─► text events enriched with │ │ inline enter/exit pairs │ │ │ └─► block enter/exit events │ + state snapshots at boundaries │ └─► Generator (offset-based, no value strings) ``` Three streaming modes, all event-well-formed (stack discipline): | Mode | API | Cost | Use case | |------|-----|------|----------| | Outline | `outlineEvents(input)` | Block only | TOC, section index | | Full | `events(input)` | Block + inline | Default, tree building | | Progressive | `parseChunked(chunks)` | Async block nodes | Streaming render | All event modes produce **range-first events**: text/token events carry `start_offset`/`end_offset` into the `TextSource` rather than an extracted `value` string. Consumers call `slice(source, evt)` to resolve text on demand. ## TextSource (`text_source.ts`) Minimal interface that abstracts the backing text store: ```ts interface TextSource { readonly length: number; charCodeAt(index: number): number; slice(start: number, end: number): string; iterSlices?(start: number, end: number): Iterable; // optional } ``` Plain `string` satisfies this interface. Rope trees, CRDTs (Yjs `Y.Text`), and append buffers can implement it for zero-copy access. The tokenizer and all downstream modules accept `TextSource`, not bare `string`. This is the collaboration-readiness seam: swapping the backing store does not require changing any parser module. ## Tokenizer (`tokenizer.ts`) Generator-based scanner using `charCodeAt` on the `TextSource`. Yields `Token` objects with offset ranges, never value strings. ### Token design (offset-based) ```ts interface Token { type: TokenType; start: number; // UTF-16 code unit offset into TextSource end: number; // exclusive end offset // No `value` field: use slice(source, token) to resolve on demand } ``` Why offsets instead of value strings: - Avoids per-token string allocation - Sidesteps V8's sliced-string retention risk (small slice *can* pin entire parent string; behavior is heuristic-driven, not unconditional) - Consumer calls `slice(source, token)` only when it needs the text - Works identically with `TextSource` implementations that aren't `string` ### Character code constants Hot scanning loops use integer character codes: | Constant | Hex | Character | Wikitext role | |------------------|--------|-----------|---------------| | `CC_EQUALS` | `0x3d` | `=` | Heading marker, template arg | | `CC_APOSTROPHE` | `0x27` | `'` | Bold/italic delimiter | | `CC_OPEN_BRACKET`| `0x5b` | `[` | Link open | | `CC_CLOSE_BRACKET`| `0x5d` | `]` | Link close | | `CC_OPEN_BRACE` | `0x7b` | `{` | Template/table open | | `CC_CLOSE_BRACE` | `0x7d` | `}` | Template/table close | | `CC_PIPE` | `0x7c` | `\|` | Table cell, template arg | | `CC_BANG` | `0x21` | `!` | Table header cell | | `CC_ASTERISK` | `0x2a` | `*` | Bullet list marker | | `CC_HASH` | `0x23` | `#` | Ordered list marker | | `CC_COLON` | `0x3a` | `:` | Definition description / indent | | `CC_SEMICOLON` | `0x3b` | `;` | Definition term | | `CC_DASH` | `0x2d` | `-` | Thematic break (`----`) | | `CC_TILDE` | `0x7e` | `~` | Signature (`~~~`+) | | `CC_UNDERSCORE` | `0x5f` | `_` | Behavior switch (`__TOC__`) | | `CC_LT` | `0x3c` | `<` | HTML/extension tag, comment | | `CC_GT` | `0x3e` | `>` | HTML tag close | | `CC_AMP` | `0x26` | `&` | HTML entity | | `CC_LF` | `0x0a` | `\n` | Line ending | | `CC_CR` | `0x0d` | `\r` | Carriage return | | `CC_SPACE` | `0x20` | ` ` | Whitespace, preformatted line | | `CC_TAB` | `0x09` | `\t` | Whitespace | ### Scanning rules - **Single pass**: never backtracks more than bounded lookahead (max 4 chars: `