@fuzdev/mdz 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/dist/Mdz.svelte +39 -0
  4. package/dist/Mdz.svelte.d.ts +17 -0
  5. package/dist/Mdz.svelte.d.ts.map +1 -0
  6. package/dist/MdzNodeView.svelte +140 -0
  7. package/dist/MdzNodeView.svelte.d.ts +9 -0
  8. package/dist/MdzNodeView.svelte.d.ts.map +1 -0
  9. package/dist/MdzPrecompiled.svelte +33 -0
  10. package/dist/MdzPrecompiled.svelte.d.ts +17 -0
  11. package/dist/MdzPrecompiled.svelte.d.ts.map +1 -0
  12. package/dist/MdzRoot.svelte +40 -0
  13. package/dist/MdzRoot.svelte.d.ts +14 -0
  14. package/dist/MdzRoot.svelte.d.ts.map +1 -0
  15. package/dist/MdzStream.svelte +37 -0
  16. package/dist/MdzStream.svelte.d.ts +18 -0
  17. package/dist/MdzStream.svelte.d.ts.map +1 -0
  18. package/dist/MdzStreamNodeView.svelte +150 -0
  19. package/dist/MdzStreamNodeView.svelte.d.ts +9 -0
  20. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -0
  21. package/dist/context_helpers.d.ts +23 -0
  22. package/dist/context_helpers.d.ts.map +1 -0
  23. package/dist/context_helpers.js +19 -0
  24. package/dist/mdz.d.ts +152 -0
  25. package/dist/mdz.d.ts.map +1 -0
  26. package/dist/mdz.js +61 -0
  27. package/dist/mdz_contexts.d.ts +106 -0
  28. package/dist/mdz_contexts.d.ts.map +1 -0
  29. package/dist/mdz_contexts.js +42 -0
  30. package/dist/mdz_helpers.d.ts +321 -0
  31. package/dist/mdz_helpers.d.ts.map +1 -0
  32. package/dist/mdz_helpers.js +668 -0
  33. package/dist/mdz_lexer.d.ts +123 -0
  34. package/dist/mdz_lexer.d.ts.map +1 -0
  35. package/dist/mdz_lexer.js +1405 -0
  36. package/dist/mdz_opcodes.d.ts +193 -0
  37. package/dist/mdz_opcodes.d.ts.map +1 -0
  38. package/dist/mdz_opcodes.js +14 -0
  39. package/dist/mdz_opcodes_to_nodes.d.ts +26 -0
  40. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -0
  41. package/dist/mdz_opcodes_to_nodes.js +410 -0
  42. package/dist/mdz_stream_parser.d.ts +109 -0
  43. package/dist/mdz_stream_parser.d.ts.map +1 -0
  44. package/dist/mdz_stream_parser.js +699 -0
  45. package/dist/mdz_stream_parser_block.d.ts +99 -0
  46. package/dist/mdz_stream_parser_block.d.ts.map +1 -0
  47. package/dist/mdz_stream_parser_block.js +437 -0
  48. package/dist/mdz_stream_parser_inline.d.ts +98 -0
  49. package/dist/mdz_stream_parser_inline.d.ts.map +1 -0
  50. package/dist/mdz_stream_parser_inline.js +481 -0
  51. package/dist/mdz_stream_parser_link.d.ts +29 -0
  52. package/dist/mdz_stream_parser_link.d.ts.map +1 -0
  53. package/dist/mdz_stream_parser_link.js +227 -0
  54. package/dist/mdz_stream_parser_list.d.ts +106 -0
  55. package/dist/mdz_stream_parser_list.d.ts.map +1 -0
  56. package/dist/mdz_stream_parser_list.js +438 -0
  57. package/dist/mdz_stream_parser_state.d.ts +476 -0
  58. package/dist/mdz_stream_parser_state.d.ts.map +1 -0
  59. package/dist/mdz_stream_parser_state.js +651 -0
  60. package/dist/mdz_stream_parser_text.d.ts +25 -0
  61. package/dist/mdz_stream_parser_text.d.ts.map +1 -0
  62. package/dist/mdz_stream_parser_text.js +67 -0
  63. package/dist/mdz_stream_parser_url.d.ts +76 -0
  64. package/dist/mdz_stream_parser_url.d.ts.map +1 -0
  65. package/dist/mdz_stream_parser_url.js +328 -0
  66. package/dist/mdz_stream_state.svelte.d.ts +53 -0
  67. package/dist/mdz_stream_state.svelte.d.ts.map +1 -0
  68. package/dist/mdz_stream_state.svelte.js +360 -0
  69. package/dist/mdz_to_svelte.d.ts +71 -0
  70. package/dist/mdz_to_svelte.d.ts.map +1 -0
  71. package/dist/mdz_to_svelte.js +127 -0
  72. package/dist/mdz_token_parser.d.ts +20 -0
  73. package/dist/mdz_token_parser.d.ts.map +1 -0
  74. package/dist/mdz_token_parser.js +449 -0
  75. package/dist/svelte_preprocess_mdz.d.ts +80 -0
  76. package/dist/svelte_preprocess_mdz.d.ts.map +1 -0
  77. package/dist/svelte_preprocess_mdz.js +543 -0
  78. package/dist/tsdoc_mdz.d.ts +45 -0
  79. package/dist/tsdoc_mdz.d.ts.map +1 -0
  80. package/dist/tsdoc_mdz.js +88 -0
  81. package/package.json +131 -0
  82. package/src/lib/context_helpers.ts +52 -0
  83. package/src/lib/mdz.ts +190 -0
  84. package/src/lib/mdz_contexts.ts +99 -0
  85. package/src/lib/mdz_helpers.ts +727 -0
  86. package/src/lib/mdz_lexer.ts +1761 -0
  87. package/src/lib/mdz_opcodes.ts +227 -0
  88. package/src/lib/mdz_opcodes_to_nodes.ts +470 -0
  89. package/src/lib/mdz_stream_parser.ts +766 -0
  90. package/src/lib/mdz_stream_parser_block.ts +490 -0
  91. package/src/lib/mdz_stream_parser_inline.ts +527 -0
  92. package/src/lib/mdz_stream_parser_link.ts +283 -0
  93. package/src/lib/mdz_stream_parser_list.ts +525 -0
  94. package/src/lib/mdz_stream_parser_state.ts +957 -0
  95. package/src/lib/mdz_stream_parser_text.ts +95 -0
  96. package/src/lib/mdz_stream_parser_url.ts +386 -0
  97. package/src/lib/mdz_stream_state.svelte.ts +383 -0
  98. package/src/lib/mdz_to_svelte.ts +213 -0
  99. package/src/lib/mdz_token_parser.ts +544 -0
  100. package/src/lib/svelte_preprocess_mdz.ts +777 -0
  101. package/src/lib/tsdoc_mdz.ts +97 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Plain-text run consumption for the streaming mdz parser.
3
+ *
4
+ * @module
5
+ */
6
+ import { ASTERISK, BACKTICK, HTTPS_PREFIX_LENGTH, H_LOWER, H_UPPER, LEFT_ANGLE, LEFT_BRACKET, NEWLINE, PERIOD, RIGHT_BRACKET, SLASH, SPACE, TAB, TILDE, UNDERSCORE, is_word_char, match_url_prefix_case_insensitive, } from './mdz_helpers.js';
7
+ import { accumulate_text, ensure_paragraph, offset, } from './mdz_stream_parser_state.js';
8
+ /**
9
+ * Consume a delimiter at the current position as literal text — the shared
10
+ * tail of every "this delimiter doesn't open/close anything here" decision
11
+ * (failed boundary, EOF degrade, greedy-rejected closer, lone `*`/`]`).
12
+ *
13
+ * @mutates `state`
14
+ * @nodocs
15
+ */
16
+ export const consume_delimiter_as_text = (state, delimiter) => {
17
+ ensure_paragraph(state);
18
+ accumulate_text(state, delimiter, offset(state));
19
+ state.prev_char = delimiter.charCodeAt(delimiter.length - 1);
20
+ state.column += delimiter.length;
21
+ state.pos += delimiter.length;
22
+ };
23
+ /**
24
+ * Consume a run of plain text characters. Scans ahead to the next
25
+ * structurally interesting character and accumulates the whole run
26
+ * as a single slice, avoiding per-character string concatenation
27
+ * and dispatch overhead.
28
+ *
29
+ * @nodocs
30
+ */
31
+ export const consume_text_run = (state) => {
32
+ ensure_paragraph(state);
33
+ const start = state.pos;
34
+ state.pos++;
35
+ while (state.pos < state.buffer.length) {
36
+ const c = state.buffer.charCodeAt(state.pos);
37
+ if (c === BACKTICK ||
38
+ c === ASTERISK ||
39
+ c === UNDERSCORE ||
40
+ c === TILDE ||
41
+ c === LEFT_BRACKET ||
42
+ c === RIGHT_BRACKET ||
43
+ c === LEFT_ANGLE ||
44
+ c === NEWLINE ||
45
+ // URL/path: only break when actually at a URL or path start.
46
+ // Most h/H/./slash in prose are not URLs/paths — continuing the scan
47
+ // avoids the full dispatch cycle through process_loop → process_inline.
48
+ // For `h`/`H` at a word boundary, also break when there isn't enough
49
+ // buffer left to confirm `https://` / `http://` — process_inline's
50
+ // speculator then carries the prefix match across chunk boundaries.
51
+ // Scheme matching is case-insensitive (RFC 3986).
52
+ ((c === H_LOWER || c === H_UPPER) &&
53
+ (match_url_prefix_case_insensitive(state.buffer, state.pos) > 0 ||
54
+ (state.buffer.length - state.pos < HTTPS_PREFIX_LENGTH &&
55
+ !is_word_char(state.buffer.charCodeAt(state.pos - 1))))) ||
56
+ ((c === SLASH || c === PERIOD) &&
57
+ (state.buffer.charCodeAt(state.pos - 1) === SPACE ||
58
+ state.buffer.charCodeAt(state.pos - 1) === NEWLINE ||
59
+ state.buffer.charCodeAt(state.pos - 1) === TAB))) {
60
+ break;
61
+ }
62
+ state.pos++;
63
+ }
64
+ accumulate_text(state, state.buffer.slice(start, state.pos), offset(state, start));
65
+ state.prev_char = state.buffer.charCodeAt(state.pos - 1);
66
+ state.column += state.pos - start;
67
+ };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Auto-URL and auto-path handlers (text-first scanning) for the streaming
3
+ * mdz parser. Operates in two phases:
4
+ *
5
+ * 1. Speculative prefix matching: chars stream as visible text while we
6
+ * verify a `https://` / `http://` prefix.
7
+ * 2. Confirmed mode: chars stream as text and on terminator a `wrap`
8
+ * opcode retroactively wraps the text node in a Link.
9
+ *
10
+ * Paths (`/...`, `./...`, `../...`) skip phase 1 — their prefix is validated
11
+ * by hold (require enough lookahead).
12
+ *
13
+ * @module
14
+ */
15
+ import { type MdzStreamParserState, type TryResult } from './mdz_stream_parser_state.js';
16
+ /**
17
+ * Forced-mode URL detection: the full prefix must be in the buffer.
18
+ * Used at EOF when speculative prefix matching isn't needed.
19
+ * Scheme match is case-insensitive (RFC 3986).
20
+ *
21
+ * @nodocs
22
+ */
23
+ export declare const try_auto_url_forced: (state: MdzStreamParserState) => TryResult;
24
+ /** @nodocs */
25
+ export declare const try_auto_path_absolute: (state: MdzStreamParserState, forced?: boolean) => TryResult;
26
+ /** @nodocs */
27
+ export declare const try_auto_path_relative: (state: MdzStreamParserState, forced?: boolean) => TryResult;
28
+ /**
29
+ * Start confirmed text-first mode for paths. Prefix already validated by hold.
30
+ * Flushes prior text, resets active_text_id, sets pending_url with confirmed=true.
31
+ *
32
+ * @nodocs
33
+ */
34
+ export declare const start_pending_url: (state: MdzStreamParserState, link_type: "external" | "internal") => void;
35
+ /**
36
+ * Start speculative text-first mode for URL prefixes.
37
+ * Chars stream as visible text while we verify `https://` or `http://`.
38
+ * Cancels if prefix doesn't match (text stays as plain text).
39
+ *
40
+ * @nodocs
41
+ */
42
+ export declare const start_speculative_url: (state: MdzStreamParserState) => void;
43
+ /**
44
+ * Speculative URL prefix matching. Checks each char against viable prefixes,
45
+ * streaming as text. Confirms on full match, cancels on mismatch.
46
+ *
47
+ * Scheme letters (`h`, `t`, `p`, `s`) are matched case-insensitively (RFC 3986);
48
+ * the `:` and `//` portion remains literal. Original casing is preserved in
49
+ * the emitted reference.
50
+ *
51
+ * @nodocs
52
+ */
53
+ export declare const process_url_prefix: (state: MdzStreamParserState) => void;
54
+ /**
55
+ * Process URL/path chars in text-first mode. Scans path chars,
56
+ * accumulates as visible text. Terminates on space/newline/non-path-char
57
+ * and emits a `wrap` opcode.
58
+ *
59
+ * @nodocs
60
+ */
61
+ export declare const process_url_content: (state: MdzStreamParserState, forced: boolean) => void;
62
+ /**
63
+ * Complete a pending URL: compute reference with trimmed punctuation,
64
+ * emit a `wrap` opcode to retroactively wrap the text node in a Link.
65
+ *
66
+ * @nodocs
67
+ */
68
+ export declare const complete_pending_url: (state: MdzStreamParserState) => void;
69
+ /**
70
+ * Forced-mode auto-link: scan the full URL/path inline (no streaming needed
71
+ * at EOF). Used when forced=true — the entire URL must be in the buffer.
72
+ *
73
+ * @nodocs
74
+ */
75
+ export declare const scan_auto_link_forced: (state: MdzStreamParserState, link_type: "external" | "internal") => TryResult;
76
+ //# sourceMappingURL=mdz_stream_parser_url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_stream_parser_url.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAcH,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,SAAS,EAOd,MAAM,8BAA8B,CAAC;AAKtC;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,oBAAoB,KAAG,SAYjE,CAAC;AAEF,cAAc;AACd,eAAO,MAAM,sBAAsB,GAAI,OAAO,oBAAoB,EAAE,gBAAc,KAAG,SAqBpF,CAAC;AAEF,cAAc;AACd,eAAO,MAAM,sBAAsB,GAAI,OAAO,oBAAoB,EAAE,gBAAc,KAAG,SAyCpF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC7B,OAAO,oBAAoB,EAC3B,WAAW,UAAU,GAAG,UAAU,KAChC,IAaF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,oBAAoB,KAAG,IAYnE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,oBAAoB,KAAG,IAwChE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,oBAAoB,EAAE,QAAQ,OAAO,KAAG,IAoClF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,oBAAoB,KAAG,IAqDlE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,oBAAoB,EAC3B,WAAW,UAAU,GAAG,UAAU,KAChC,SA2CF,CAAC"}
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Auto-URL and auto-path handlers (text-first scanning) for the streaming
3
+ * mdz parser. Operates in two phases:
4
+ *
5
+ * 1. Speculative prefix matching: chars stream as visible text while we
6
+ * verify a `https://` / `http://` prefix.
7
+ * 2. Confirmed mode: chars stream as text and on terminator a `wrap`
8
+ * opcode retroactively wraps the text node in a Link.
9
+ *
10
+ * Paths (`/...`, `./...`, `../...`) skip phase 1 — their prefix is validated
11
+ * by hold (require enough lookahead).
12
+ *
13
+ * @module
14
+ */
15
+ import { NEWLINE, SPACE, TAB, PERIOD, SLASH, ascii_to_lower, is_valid_path_char, is_word_char, match_url_prefix_case_insensitive, mdz_trim_url_punctuation, } from './mdz_helpers.js';
16
+ import { accumulate_text, alloc_id, emit, ensure_paragraph, flush_text, offset, } from './mdz_stream_parser_state.js';
17
+ const HTTPS_PREFIX = 'https://';
18
+ const HTTP_PREFIX = 'http://';
19
+ /**
20
+ * Forced-mode URL detection: the full prefix must be in the buffer.
21
+ * Used at EOF when speculative prefix matching isn't needed.
22
+ * Scheme match is case-insensitive (RFC 3986).
23
+ *
24
+ * @nodocs
25
+ */
26
+ export const try_auto_url_forced = (state) => {
27
+ if (state.prev_char !== -1 && is_word_char(state.prev_char))
28
+ return 'not_match';
29
+ const prefix_len = match_url_prefix_case_insensitive(state.buffer, state.pos);
30
+ if (prefix_len === 0)
31
+ return 'not_match';
32
+ // must have content after protocol
33
+ if (state.pos + prefix_len >= state.buffer.length)
34
+ return 'not_match';
35
+ const after = state.buffer.charCodeAt(state.pos + prefix_len);
36
+ if (after === SPACE || after === NEWLINE)
37
+ return 'not_match';
38
+ return scan_auto_link_forced(state, 'external');
39
+ };
40
+ /** @nodocs */
41
+ export const try_auto_path_absolute = (state, forced = false) => {
42
+ // must be at word boundary (preceded by space/newline/start)
43
+ if (state.prev_char !== -1 &&
44
+ state.prev_char !== SPACE &&
45
+ state.prev_char !== NEWLINE &&
46
+ state.prev_char !== TAB) {
47
+ return 'not_match';
48
+ }
49
+ // reject // and /space
50
+ if (state.pos + 1 >= state.buffer.length)
51
+ return forced ? 'not_match' : 'need_more';
52
+ const next = state.buffer.charCodeAt(state.pos + 1);
53
+ if (next === SLASH || next === SPACE || next === NEWLINE || next === TAB) {
54
+ return 'not_match';
55
+ }
56
+ if (forced)
57
+ return scan_auto_link_forced(state, 'internal');
58
+ start_pending_url(state, 'internal');
59
+ return 'consumed';
60
+ };
61
+ /** @nodocs */
62
+ export const try_auto_path_relative = (state, forced = false) => {
63
+ if (state.prev_char !== -1 &&
64
+ state.prev_char !== SPACE &&
65
+ state.prev_char !== NEWLINE &&
66
+ state.prev_char !== TAB) {
67
+ return 'not_match';
68
+ }
69
+ // check for ./ or ../
70
+ const remaining = state.buffer.length - state.pos;
71
+ if (remaining < 3)
72
+ return forced ? 'not_match' : 'need_more';
73
+ if (state.buffer.charCodeAt(state.pos + 1) === PERIOD &&
74
+ state.buffer.charCodeAt(state.pos + 2) === SLASH) {
75
+ // ../
76
+ if (remaining < 4)
77
+ return forced ? 'not_match' : 'need_more';
78
+ const after = state.buffer.charCodeAt(state.pos + 3);
79
+ if (after === SPACE || after === NEWLINE || after === TAB || after === SLASH) {
80
+ return 'not_match';
81
+ }
82
+ if (forced)
83
+ return scan_auto_link_forced(state, 'internal');
84
+ start_pending_url(state, 'internal');
85
+ return 'consumed';
86
+ }
87
+ if (state.buffer.charCodeAt(state.pos + 1) === SLASH) {
88
+ // ./
89
+ const after = state.buffer.charCodeAt(state.pos + 2);
90
+ if (after === SPACE || after === NEWLINE || after === TAB || after === SLASH) {
91
+ return 'not_match';
92
+ }
93
+ if (forced)
94
+ return scan_auto_link_forced(state, 'internal');
95
+ start_pending_url(state, 'internal');
96
+ return 'consumed';
97
+ }
98
+ return 'not_match';
99
+ };
100
+ /**
101
+ * Start confirmed text-first mode for paths. Prefix already validated by hold.
102
+ * Flushes prior text, resets active_text_id, sets pending_url with confirmed=true.
103
+ *
104
+ * @nodocs
105
+ */
106
+ export const start_pending_url = (state, link_type) => {
107
+ flush_text(state);
108
+ ensure_paragraph(state);
109
+ state.active_text_id = null;
110
+ state.pending_url = {
111
+ url_text: '',
112
+ start: offset(state),
113
+ link_type,
114
+ confirmed: true,
115
+ viable_https: false,
116
+ viable_http: false,
117
+ };
118
+ // don't advance pos — URL chars start at current position, handled by process_url_content
119
+ };
120
+ /**
121
+ * Start speculative text-first mode for URL prefixes.
122
+ * Chars stream as visible text while we verify `https://` or `http://`.
123
+ * Cancels if prefix doesn't match (text stays as plain text).
124
+ *
125
+ * @nodocs
126
+ */
127
+ export const start_speculative_url = (state) => {
128
+ flush_text(state);
129
+ ensure_paragraph(state);
130
+ state.active_text_id = null;
131
+ state.pending_url = {
132
+ url_text: '',
133
+ start: offset(state),
134
+ link_type: 'external',
135
+ confirmed: false,
136
+ viable_https: true,
137
+ viable_http: true,
138
+ };
139
+ };
140
+ /**
141
+ * Speculative URL prefix matching. Checks each char against viable prefixes,
142
+ * streaming as text. Confirms on full match, cancels on mismatch.
143
+ *
144
+ * Scheme letters (`h`, `t`, `p`, `s`) are matched case-insensitively (RFC 3986);
145
+ * the `:` and `//` portion remains literal. Original casing is preserved in
146
+ * the emitted reference.
147
+ *
148
+ * @nodocs
149
+ */
150
+ export const process_url_prefix = (state) => {
151
+ const pu = state.pending_url;
152
+ const char_code = state.buffer.charCodeAt(state.pos);
153
+ const pos = pu.url_text.length;
154
+ // scheme letters (positions 0-4) match case-insensitively;
155
+ // `:` and `/` (positions 5-7 for https, 4-6 for http) are literal.
156
+ const cmp_code = pos < 5 ? ascii_to_lower(char_code) : char_code;
157
+ // narrow viable prefixes
158
+ if (pu.viable_https &&
159
+ (pos >= HTTPS_PREFIX.length || cmp_code !== HTTPS_PREFIX.charCodeAt(pos))) {
160
+ pu.viable_https = false;
161
+ }
162
+ if (pu.viable_http && (pos >= HTTP_PREFIX.length || cmp_code !== HTTP_PREFIX.charCodeAt(pos))) {
163
+ pu.viable_http = false;
164
+ }
165
+ if (!pu.viable_https && !pu.viable_http) {
166
+ // no viable prefix — cancel speculation, don't consume char
167
+ state.pending_url = null;
168
+ return;
169
+ }
170
+ // char matches — accumulate as text
171
+ accumulate_text(state, state.buffer[state.pos], offset(state));
172
+ pu.url_text += state.buffer[state.pos];
173
+ state.pos++;
174
+ state.column++;
175
+ state.prev_char = char_code;
176
+ // check if a prefix is fully confirmed
177
+ if ((pu.viable_https && pos + 1 === HTTPS_PREFIX.length) ||
178
+ (pu.viable_http && pos + 1 === HTTP_PREFIX.length)) {
179
+ pu.confirmed = true;
180
+ }
181
+ };
182
+ /**
183
+ * Process URL/path chars in text-first mode. Scans path chars,
184
+ * accumulates as visible text. Terminates on space/newline/non-path-char
185
+ * and emits a `wrap` opcode.
186
+ *
187
+ * @nodocs
188
+ */
189
+ export const process_url_content = (state, forced) => {
190
+ // speculative prefix matching phase
191
+ if (!state.pending_url.confirmed) {
192
+ process_url_prefix(state);
193
+ return;
194
+ }
195
+ const char_code = state.buffer.charCodeAt(state.pos);
196
+ // URL terminator
197
+ if (char_code === SPACE || char_code === NEWLINE || !is_valid_path_char(char_code)) {
198
+ flush_text(state);
199
+ complete_pending_url(state);
200
+ // don't consume terminator — outer loop handles it
201
+ return;
202
+ }
203
+ // scan URL chars
204
+ const start = state.pos;
205
+ state.pos++;
206
+ while (state.pos < state.buffer.length) {
207
+ const c = state.buffer.charCodeAt(state.pos);
208
+ if (c === SPACE || c === NEWLINE || !is_valid_path_char(c))
209
+ break;
210
+ state.pos++;
211
+ }
212
+ const text = state.buffer.slice(start, state.pos);
213
+ accumulate_text(state, text, offset(state, start));
214
+ state.pending_url.url_text += text;
215
+ state.prev_char = state.buffer.charCodeAt(state.pos - 1);
216
+ state.column += state.pos - start;
217
+ // at EOF in forced mode, finalize the URL
218
+ if (state.pos >= state.buffer.length && forced) {
219
+ flush_text(state);
220
+ complete_pending_url(state);
221
+ }
222
+ };
223
+ /**
224
+ * Complete a pending URL: compute reference with trimmed punctuation,
225
+ * emit a `wrap` opcode to retroactively wrap the text node in a Link.
226
+ *
227
+ * @nodocs
228
+ */
229
+ export const complete_pending_url = (state) => {
230
+ const pu = state.pending_url;
231
+ state.pending_url = null;
232
+ // unconfirmed speculative prefix (e.g. a trailing `ht` at EOF) can never
233
+ // become a URL — paths start confirmed, URLs confirm only on a full
234
+ // `https://`/`http://` match; text stays as plain text
235
+ if (!pu.confirmed)
236
+ return;
237
+ const reference = mdz_trim_url_punctuation(pu.url_text);
238
+ if (reference.length === 0)
239
+ return; // empty after trimming — text stays as plain text
240
+ // for external URLs, must have content after protocol prefix
241
+ if (pu.link_type === 'external') {
242
+ const prefix_len = match_url_prefix_case_insensitive(reference, 0);
243
+ if (reference.length < prefix_len + 1)
244
+ return; // just protocol, no content
245
+ }
246
+ const text_id = state.active_text_id;
247
+ if (text_id === null)
248
+ return; // no text node to wrap (shouldn't happen)
249
+ const trim_end = pu.url_text.length - reference.length;
250
+ const wrap_id = alloc_id(state);
251
+ const url_end = pu.start + reference.length;
252
+ if (trim_end > 0) {
253
+ const trim_id = alloc_id(state);
254
+ emit(state, {
255
+ type: 'wrap',
256
+ id: wrap_id,
257
+ node_type: 'Link',
258
+ target_id: text_id,
259
+ reference,
260
+ link_type: pu.link_type,
261
+ start: pu.start,
262
+ end: url_end,
263
+ trim_end,
264
+ trim_id,
265
+ });
266
+ state.active_text_id = trim_id;
267
+ }
268
+ else {
269
+ emit(state, {
270
+ type: 'wrap',
271
+ id: wrap_id,
272
+ node_type: 'Link',
273
+ target_id: text_id,
274
+ reference,
275
+ link_type: pu.link_type,
276
+ start: pu.start,
277
+ end: url_end,
278
+ });
279
+ state.active_text_id = null;
280
+ }
281
+ };
282
+ /**
283
+ * Forced-mode auto-link: scan the full URL/path inline (no streaming needed
284
+ * at EOF). Used when forced=true — the entire URL must be in the buffer.
285
+ *
286
+ * @nodocs
287
+ */
288
+ export const scan_auto_link_forced = (state, link_type) => {
289
+ let i = state.pos;
290
+ while (i < state.buffer.length) {
291
+ const c = state.buffer.charCodeAt(i);
292
+ if (c === SPACE || c === NEWLINE || !is_valid_path_char(c))
293
+ break;
294
+ i++;
295
+ }
296
+ let reference = state.buffer.slice(state.pos, i);
297
+ reference = mdz_trim_url_punctuation(reference);
298
+ if (reference.length === 0)
299
+ return 'not_match';
300
+ // for external URLs, must have content after protocol prefix
301
+ if (link_type === 'external') {
302
+ const prefix_len = match_url_prefix_case_insensitive(reference, 0);
303
+ if (reference.length < prefix_len + 1)
304
+ return 'not_match';
305
+ }
306
+ const end_pos = state.pos + reference.length;
307
+ const link_start = offset(state);
308
+ const link_end = offset(state, end_pos);
309
+ flush_text(state);
310
+ ensure_paragraph(state);
311
+ const link_id = alloc_id(state);
312
+ emit(state, { type: 'open', id: link_id, node_type: 'Link', start: link_start });
313
+ const text_id = alloc_id(state);
314
+ emit(state, {
315
+ type: 'text',
316
+ id: text_id,
317
+ content: reference,
318
+ text_type: 'Text',
319
+ start: link_start,
320
+ end: link_end,
321
+ });
322
+ emit(state, { type: 'close', id: link_id, end: link_end, reference, link_type });
323
+ state.active_text_id = null;
324
+ state.column += end_pos - state.pos;
325
+ state.prev_char = reference.charCodeAt(reference.length - 1);
326
+ state.pos = end_pos;
327
+ return 'consumed';
328
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Reactive state for the mdz streaming renderer.
3
+ *
4
+ * Maintains a reactive tree of `MdzStreamNode` objects that Svelte 5 can
5
+ * efficiently update. Opcodes from `MdzStreamParser` are applied via
6
+ * `apply()` or `apply_batch()`, mutating the tree in place.
7
+ *
8
+ * @module
9
+ */
10
+ import type { MdzNodeId, MdzNodeType, MdzOpcode } from './mdz_opcodes.js';
11
+ /**
12
+ * A reactive node in the stream renderer tree.
13
+ * Fields are `$state` so Svelte re-renders only what changes.
14
+ */
15
+ export declare class MdzStreamNode {
16
+ id: MdzNodeId;
17
+ type: MdzNodeType;
18
+ content: string;
19
+ children: Array<MdzStreamNode>;
20
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
21
+ reference?: string;
22
+ link_type?: 'external' | 'internal';
23
+ name?: string;
24
+ lang?: string | null;
25
+ heading_id?: string;
26
+ ordered?: boolean;
27
+ start_number?: number;
28
+ number?: number;
29
+ constructor(id: MdzNodeId, type: MdzNodeType);
30
+ }
31
+ /**
32
+ * Reactive state manager for streaming mdz content.
33
+ * Apply opcodes to incrementally build and update the render tree.
34
+ *
35
+ * Lifecycle: one state instance per stream, paired with one `MdzStreamParser`.
36
+ * Opcodes must be applied exactly once, in emission order — `take_opcodes()`
37
+ * batches can be split or concatenated freely, but never reordered, skipped,
38
+ * or replayed. There is no `reset()`; to restart a stream, construct a new
39
+ * parser and a new state.
40
+ */
41
+ export declare class MdzStreamState {
42
+ #private;
43
+ root: Array<MdzStreamNode>;
44
+ /**
45
+ * Apply a single opcode.
46
+ */
47
+ apply(opcode: MdzOpcode): void;
48
+ /**
49
+ * Apply a batch of opcodes.
50
+ */
51
+ apply_batch(opcodes: Array<MdzOpcode>): void;
52
+ }
53
+ //# sourceMappingURL=mdz_stream_state.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_stream_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAC,SAAS,EAAE,WAAW,EAAE,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAExE;;;GAGG;AACH,qBAAa,aAAa;IACzB,EAAE,EAAE,SAAS,CAAC;IACd,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAc;IAC7B,QAAQ,EAAE,KAAK,CAAC,aAAa,CAAC,CAAc;IAE5C,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAY;IAC9B,SAAS,CAAC,EAAE,UAAU,GAAG,UAAU,CAAY;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAY;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEJ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW;CAI5C;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAc;;IAC1B,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,CAAc;IAqCxC;;OAEG;IACH,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IA2R9B;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI;CAK5C"}