@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,99 @@
1
+ /**
2
+ * Block-element handlers for the streaming mdz parser.
3
+ *
4
+ * Each `try_*` returns {@link TryResult} so the orchestrator in
5
+ * {@link ./mdz_stream_parser.ts} can fall through or wait for more input.
6
+ *
7
+ * @module
8
+ */
9
+ import { type CodeblockState, type MdzStreamParserState, type TryResult } from './mdz_stream_parser_state.js';
10
+ /**
11
+ * Try to parse a heading at column 0.
12
+ *
13
+ * @nodocs
14
+ */
15
+ export declare const try_heading: (state: MdzStreamParserState) => TryResult;
16
+ /**
17
+ * Try to parse a horizontal rule at column 0.
18
+ * When `forced` is true, treats buffer end as EOF (no more input coming).
19
+ *
20
+ * @nodocs
21
+ */
22
+ export declare const try_hr: (state: MdzStreamParserState, forced?: boolean) => TryResult;
23
+ /**
24
+ * Try to open a code block at column 0. A complete opener line is decisive —
25
+ * raw mode is absolute, so fence-ness never depends on a closer arriving:
26
+ * an unclosed fence consumes to EOF as content. Any open paragraph closes
27
+ * first (fences interrupt paragraphs, matching the sync parser). The only
28
+ * hold is the opener line itself; an opener cut off at EOF without its
29
+ * newline stays text (the orchestrator treats `need_more` as text in forced
30
+ * mode).
31
+ *
32
+ * @nodocs
33
+ */
34
+ export declare const try_codeblock_open: (state: MdzStreamParserState) => TryResult;
35
+ /**
36
+ * Emit codeblock content as `text` (first chunk) or `append_text` (subsequent
37
+ * chunks), updating `cb.text_id` on first emit. `content_start` is the local
38
+ * buffer position where the content slice began — only used to compute the
39
+ * global byte offset for the first `text` opcode.
40
+ *
41
+ * @nodocs
42
+ */
43
+ export declare const emit_codeblock_text: (state: MdzStreamParserState, cb: CodeblockState, content: string, content_start: number) => void;
44
+ /**
45
+ * Drain the remaining buffer as codeblock content at EOF, looking for a closing
46
+ * fence whose terminator is EOF rather than `\n`. Matches the sync lexer's
47
+ * `#match_fence`, which allows a fence at the end of the document.
48
+ *
49
+ * If a fence is found, the codeblock closes normally. Otherwise the remaining
50
+ * buffer becomes content and `close_codeblock_at_eof` (in
51
+ * `mdz_stream_parser_state.ts`) emits the plain close after process completes
52
+ * (an unclosed fence consumes to EOF — never reverted).
53
+ *
54
+ * @nodocs
55
+ */
56
+ export declare const process_codeblock_forced: (state: MdzStreamParserState) => void;
57
+ /**
58
+ * Process bytes while in codeblock mode.
59
+ * Returns true if processing should continue, false if more input needed.
60
+ *
61
+ * @nodocs
62
+ */
63
+ export declare const process_codeblock: (state: MdzStreamParserState) => boolean;
64
+ /**
65
+ * Process buffered lines while inside an in-item code block
66
+ * (`fence_indent > 0`). Operates on complete lines only — a partial line at
67
+ * the buffer end holds until its newline arrives (line-bounded, like the
68
+ * opener hold), keeping the per-line indent strip and closer probe
69
+ * deterministic across chunk boundaries. Content lines strip
70
+ * `min(fence_indent, line_indent)` and emit joined by `'\n'` separators
71
+ * (first line bare, later lines prefixed), so emitted content never carries
72
+ * the region's final newline — `close_codeblock_at_eof` therefore skips its
73
+ * trailing-newline trim for in-item fences.
74
+ *
75
+ * A closing fence — any indent <= the opener's, a run of >= the opener's
76
+ * backticks, only whitespace to the line end — closes the block with `pos`
77
+ * left at its terminating newline, so the list line classifier dispatches
78
+ * what follows (mirroring the sync lexer's loop shape). In forced mode EOF
79
+ * terminates closer and content lines.
80
+ *
81
+ * Returns `false` when more input is needed; never in forced mode.
82
+ *
83
+ * @nodocs
84
+ */
85
+ export declare const process_codeblock_in_item: (state: MdzStreamParserState, forced: boolean) => boolean;
86
+ /**
87
+ * Check if the buffer at current position has a codeblock closing fence —
88
+ * a run of at least `backtick_count` backticks with only whitespace to end
89
+ * of line. Returns the position after the fence (including trailing newline)
90
+ * on success, `-1` when definitely not a fence, or `-2` when more input is
91
+ * needed to decide.
92
+ *
93
+ * When `forced` is true, EOF after the fence counts as a valid terminator
94
+ * (matches `mdz_parse`'s handling of code blocks at end of input).
95
+ *
96
+ * @nodocs
97
+ */
98
+ export declare const match_codeblock_close: (state: MdzStreamParserState, backtick_count: number, forced?: boolean) => number;
99
+ //# sourceMappingURL=mdz_stream_parser_block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_stream_parser_block.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_block.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EAOd,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,oBAAoB,KAAG,SAuDzD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,oBAAoB,EAAE,gBAAc,KAAG,SA4CpE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,oBAAoB,KAAG,SAgDhE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,OAAO,oBAAoB,EAC3B,IAAI,cAAc,EAClB,SAAS,MAAM,EACf,eAAe,MAAM,KACnB,IAgBF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,oBAAoB,KAAG,IA6CtE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,oBAAoB,KAAG,OA2D/D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,yBAAyB,GACrC,OAAO,oBAAoB,EAC3B,QAAQ,OAAO,KACb,OAmDF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,oBAAoB,EAC3B,gBAAgB,MAAM,EACtB,SAAQ,OAAe,KACrB,MAgCF,CAAC"}
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Block-element handlers for the streaming mdz parser.
3
+ *
4
+ * Each `try_*` returns {@link TryResult} so the orchestrator in
5
+ * {@link ./mdz_stream_parser.ts} can fall through or wait for more input.
6
+ *
7
+ * @module
8
+ */
9
+ import { BACKTICK, HASH, HR_HYPHEN_COUNT, HYPHEN, MAX_HEADING_LEVEL, MIN_CODEBLOCK_BACKTICKS, NEWLINE, SPACE, is_line_whitespace, } from './mdz_helpers.js';
10
+ import { alloc_id, close_paragraph, emit, flush_text, offset, push_stack_entry, } from './mdz_stream_parser_state.js';
11
+ /**
12
+ * Try to parse a heading at column 0.
13
+ *
14
+ * @nodocs
15
+ */
16
+ export const try_heading = (state) => {
17
+ const start = state.pos;
18
+ let i = start;
19
+ // count hashes
20
+ let hash_count = 0;
21
+ while (i < state.buffer.length &&
22
+ state.buffer.charCodeAt(i) === HASH &&
23
+ hash_count <= MAX_HEADING_LEVEL) {
24
+ hash_count++;
25
+ i++;
26
+ }
27
+ if (hash_count === 0 || hash_count > MAX_HEADING_LEVEL)
28
+ return 'not_match';
29
+ // need at least one more char to check for space
30
+ if (i >= state.buffer.length)
31
+ return 'need_more';
32
+ // must have space after hashes
33
+ if (state.buffer.charCodeAt(i) !== SPACE)
34
+ return 'not_match';
35
+ i++; // consume space
36
+ // must have at least one non-whitespace char before the newline (mirrors
37
+ // the sync lexer's has-content probe) — hold until it can be decided. A
38
+ // line-bounded hold: the heading opens the moment its first content char
39
+ // arrives. In forced mode the orchestrator treats `need_more` as text.
40
+ let j = i;
41
+ while (j < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(j))) {
42
+ j++;
43
+ }
44
+ if (j >= state.buffer.length)
45
+ return 'need_more';
46
+ if (state.buffer.charCodeAt(j) === NEWLINE)
47
+ return 'not_match'; // no content — literal text
48
+ // close any open paragraph first
49
+ flush_text(state);
50
+ close_paragraph(state);
51
+ const id = alloc_id(state);
52
+ const heading_start = offset(state, start);
53
+ emit(state, {
54
+ type: 'open',
55
+ id,
56
+ node_type: 'Heading',
57
+ start: heading_start,
58
+ level: hash_count,
59
+ });
60
+ push_stack_entry(state, id, 'Heading', heading_start);
61
+ state.in_heading = true;
62
+ state.heading_text_parts = [''];
63
+ state.pos = i;
64
+ state.column = i - start;
65
+ state.prev_char = SPACE;
66
+ return 'consumed';
67
+ };
68
+ /**
69
+ * Try to parse a horizontal rule at column 0.
70
+ * When `forced` is true, treats buffer end as EOF (no more input coming).
71
+ *
72
+ * @nodocs
73
+ */
74
+ export const try_hr = (state, forced = false) => {
75
+ const start = state.pos;
76
+ let i = start;
77
+ // must have exactly 3 hyphens
78
+ if (i + HR_HYPHEN_COUNT > state.buffer.length)
79
+ return 'need_more';
80
+ for (let j = 0; j < HR_HYPHEN_COUNT; j++) {
81
+ if (state.buffer.charCodeAt(i + j) !== HYPHEN)
82
+ return 'not_match';
83
+ }
84
+ i += HR_HYPHEN_COUNT;
85
+ // after hyphens, only line whitespace allowed before newline/EOF
86
+ while (i < state.buffer.length) {
87
+ const c = state.buffer.charCodeAt(i);
88
+ if (c === NEWLINE)
89
+ break;
90
+ if (!is_line_whitespace(c))
91
+ return 'not_match';
92
+ i++;
93
+ }
94
+ // if buffer ends without newline/EOF confirmation, need more input (unless forced)
95
+ if (i >= state.buffer.length && !forced) {
96
+ return 'need_more';
97
+ }
98
+ flush_text(state);
99
+ close_paragraph(state);
100
+ const id = alloc_id(state);
101
+ emit(state, {
102
+ type: 'void',
103
+ id,
104
+ node_type: 'Hr',
105
+ start: offset(state, start),
106
+ end: offset(state, i),
107
+ });
108
+ // `i` sits at the line's newline, or at the buffer end — in forced mode
109
+ // always the buffer end (a newline would have resolved this line during
110
+ // `feed`, so a forced HR is only ever the document's final line). Trailing
111
+ // newline and blank lines absorb at the process-loop top.
112
+ state.pos = i;
113
+ state.skip_blank_lines = true;
114
+ state.column = 0;
115
+ state.prev_char = NEWLINE;
116
+ return 'consumed';
117
+ };
118
+ /**
119
+ * Try to open a code block at column 0. A complete opener line is decisive —
120
+ * raw mode is absolute, so fence-ness never depends on a closer arriving:
121
+ * an unclosed fence consumes to EOF as content. Any open paragraph closes
122
+ * first (fences interrupt paragraphs, matching the sync parser). The only
123
+ * hold is the opener line itself; an opener cut off at EOF without its
124
+ * newline stays text (the orchestrator treats `need_more` as text in forced
125
+ * mode).
126
+ *
127
+ * @nodocs
128
+ */
129
+ export const try_codeblock_open = (state) => {
130
+ const start = state.pos;
131
+ let i = start;
132
+ // count backticks
133
+ let backtick_count = 0;
134
+ while (i < state.buffer.length && state.buffer.charCodeAt(i) === BACKTICK) {
135
+ backtick_count++;
136
+ i++;
137
+ }
138
+ if (backtick_count < MIN_CODEBLOCK_BACKTICKS) {
139
+ // if buffer ended mid-backtick-sequence, need more input
140
+ if (i >= state.buffer.length)
141
+ return 'need_more';
142
+ // definitely not enough backticks
143
+ return 'not_match';
144
+ }
145
+ // find end of opening fence line (need newline)
146
+ const lang_start = i;
147
+ while (i < state.buffer.length) {
148
+ const c = state.buffer.charCodeAt(i);
149
+ if (c === NEWLINE || is_line_whitespace(c))
150
+ break;
151
+ i++;
152
+ }
153
+ const lang = i > lang_start ? state.buffer.slice(lang_start, i) : null;
154
+ // skip trailing whitespace on opening fence line
155
+ while (i < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(i))) {
156
+ i++;
157
+ }
158
+ // must find newline
159
+ if (i >= state.buffer.length)
160
+ return 'need_more';
161
+ if (state.buffer.charCodeAt(i) !== NEWLINE)
162
+ return 'not_match';
163
+ i++; // consume newline
164
+ flush_text(state);
165
+ close_paragraph(state);
166
+ const id = alloc_id(state);
167
+ const cb_start = offset(state, start);
168
+ emit(state, { type: 'open', id, node_type: 'Codeblock', start: cb_start, lang });
169
+ state.codeblock = { id, backtick_count, text_id: null, fence_indent: 0, start: cb_start };
170
+ state.pos = i;
171
+ state.column = 0;
172
+ state.prev_char = NEWLINE;
173
+ return 'consumed';
174
+ };
175
+ /**
176
+ * Emit codeblock content as `text` (first chunk) or `append_text` (subsequent
177
+ * chunks), updating `cb.text_id` on first emit. `content_start` is the local
178
+ * buffer position where the content slice began — only used to compute the
179
+ * global byte offset for the first `text` opcode.
180
+ *
181
+ * @nodocs
182
+ */
183
+ export const emit_codeblock_text = (state, cb, content, content_start) => {
184
+ if (cb.text_id !== null) {
185
+ emit(state, { type: 'append_text', id: cb.text_id, content });
186
+ }
187
+ else {
188
+ const id = alloc_id(state);
189
+ const text_start = offset(state, content_start);
190
+ emit(state, {
191
+ type: 'text',
192
+ id,
193
+ content,
194
+ text_type: 'Text',
195
+ start: text_start,
196
+ end: text_start + content.length,
197
+ });
198
+ cb.text_id = id;
199
+ }
200
+ };
201
+ /**
202
+ * Drain the remaining buffer as codeblock content at EOF, looking for a closing
203
+ * fence whose terminator is EOF rather than `\n`. Matches the sync lexer's
204
+ * `#match_fence`, which allows a fence at the end of the document.
205
+ *
206
+ * If a fence is found, the codeblock closes normally. Otherwise the remaining
207
+ * buffer becomes content and `close_codeblock_at_eof` (in
208
+ * `mdz_stream_parser_state.ts`) emits the plain close after process completes
209
+ * (an unclosed fence consumes to EOF — never reverted).
210
+ *
211
+ * @nodocs
212
+ */
213
+ export const process_codeblock_forced = (state) => {
214
+ const cb = state.codeblock;
215
+ const start = state.pos;
216
+ while (state.pos < state.buffer.length) {
217
+ if (state.column === 0) {
218
+ const fence_match = match_codeblock_close(state, cb.backtick_count, true);
219
+ if (fence_match !== -1) {
220
+ // emit content up to the closing fence (excluding trailing newline before fence)
221
+ let content_end = state.pos;
222
+ if (content_end > start && state.buffer.charCodeAt(content_end - 1) === NEWLINE) {
223
+ content_end--;
224
+ }
225
+ const content = state.buffer.slice(start, content_end);
226
+ if (content.length > 0) {
227
+ emit_codeblock_text(state, cb, content, start);
228
+ }
229
+ // the closer here is always EOF-terminated (`fence_match` is the
230
+ // buffer end): a newline-terminated closer resolves during `feed`,
231
+ // whose hold retains at most a trailing newline plus a partial
232
+ // fence line. So — like the non-forced path's `fence_match - 1` —
233
+ // the close `end` excludes any line terminator
234
+ emit(state, { type: 'close', id: cb.id, end: offset(state, fence_match) });
235
+ state.pos = fence_match;
236
+ state.codeblock = null;
237
+ return;
238
+ }
239
+ }
240
+ const c = state.buffer.charCodeAt(state.pos);
241
+ if (c === NEWLINE) {
242
+ state.column = 0;
243
+ }
244
+ else {
245
+ state.column++;
246
+ }
247
+ state.pos++;
248
+ }
249
+ // no closing fence — dump the rest as codeblock content; caller's
250
+ // `close_codeblock_at_eof` then closes the codeblock at EOF
251
+ const remaining = state.buffer.slice(start, state.pos);
252
+ if (remaining.length > 0) {
253
+ emit_codeblock_text(state, cb, remaining, start);
254
+ }
255
+ };
256
+ /**
257
+ * Process bytes while in codeblock mode.
258
+ * Returns true if processing should continue, false if more input needed.
259
+ *
260
+ * @nodocs
261
+ */
262
+ export const process_codeblock = (state) => {
263
+ const cb = state.codeblock;
264
+ const start = state.pos;
265
+ while (state.pos < state.buffer.length) {
266
+ // check for closing fence at column 0
267
+ if (state.column === 0) {
268
+ const fence_match = match_codeblock_close(state, cb.backtick_count);
269
+ if (fence_match === -2) {
270
+ // need more input — hold from this position (don't consume potential fence chars)
271
+ break;
272
+ }
273
+ if (fence_match !== -1) {
274
+ // emit content up to the closing fence (excluding trailing newline before fence)
275
+ let content_end = state.pos;
276
+ if (content_end > start && state.buffer.charCodeAt(content_end - 1) === NEWLINE) {
277
+ content_end--;
278
+ }
279
+ const content = state.buffer.slice(start, content_end);
280
+ if (content.length > 0) {
281
+ emit_codeblock_text(state, cb, content, start);
282
+ }
283
+ // codeblock end = position of newline after closing fence (exclusive)
284
+ emit(state, { type: 'close', id: cb.id, end: offset(state, fence_match - 1) });
285
+ state.pos = fence_match;
286
+ state.codeblock = null;
287
+ state.column = 0;
288
+ state.prev_char = NEWLINE;
289
+ // trailing newlines and blank lines absorb at the process-loop top
290
+ state.skip_blank_lines = true;
291
+ return true;
292
+ }
293
+ }
294
+ // not a closing fence — advance
295
+ const c = state.buffer.charCodeAt(state.pos);
296
+ if (c === NEWLINE) {
297
+ state.column = 0;
298
+ }
299
+ else {
300
+ state.column++;
301
+ }
302
+ state.pos++;
303
+ }
304
+ // buffer exhausted or broke for potential fence — emit content so far, hold trailing data.
305
+ // Hold trailing \n (could be before a closing fence in next chunk), and also hold
306
+ // \n + backticks when we broke mid-fence-check (the \n precedes the backticks and
307
+ // would be stripped by fence close, so must not be emitted as content yet).
308
+ let emit_end = state.pos;
309
+ if (emit_end > start && state.buffer.charCodeAt(emit_end - 1) === NEWLINE) {
310
+ emit_end--;
311
+ state.pos = emit_end; // leave \n (+ any held backticks after it) in buffer
312
+ }
313
+ const content = state.buffer.slice(start, emit_end);
314
+ if (content.length > 0) {
315
+ emit_codeblock_text(state, cb, content, start);
316
+ }
317
+ return false; // need more input
318
+ };
319
+ /**
320
+ * Process buffered lines while inside an in-item code block
321
+ * (`fence_indent > 0`). Operates on complete lines only — a partial line at
322
+ * the buffer end holds until its newline arrives (line-bounded, like the
323
+ * opener hold), keeping the per-line indent strip and closer probe
324
+ * deterministic across chunk boundaries. Content lines strip
325
+ * `min(fence_indent, line_indent)` and emit joined by `'\n'` separators
326
+ * (first line bare, later lines prefixed), so emitted content never carries
327
+ * the region's final newline — `close_codeblock_at_eof` therefore skips its
328
+ * trailing-newline trim for in-item fences.
329
+ *
330
+ * A closing fence — any indent <= the opener's, a run of >= the opener's
331
+ * backticks, only whitespace to the line end — closes the block with `pos`
332
+ * left at its terminating newline, so the list line classifier dispatches
333
+ * what follows (mirroring the sync lexer's loop shape). In forced mode EOF
334
+ * terminates closer and content lines.
335
+ *
336
+ * Returns `false` when more input is needed; never in forced mode.
337
+ *
338
+ * @nodocs
339
+ */
340
+ export const process_codeblock_in_item = (state, forced) => {
341
+ const cb = state.codeblock;
342
+ while (state.pos < state.buffer.length) {
343
+ const line_start = state.pos;
344
+ let j = line_start;
345
+ while (j < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(j))) {
346
+ j++;
347
+ }
348
+ if (j >= state.buffer.length && !forced)
349
+ return false; // partial line — hold
350
+ const line_indent = j - line_start;
351
+ // closer probe
352
+ if (line_indent <= cb.fence_indent &&
353
+ j < state.buffer.length &&
354
+ state.buffer.charCodeAt(j) === BACKTICK) {
355
+ let k = j;
356
+ let count = 0;
357
+ while (k < state.buffer.length && state.buffer.charCodeAt(k) === BACKTICK) {
358
+ count++;
359
+ k++;
360
+ }
361
+ if (k >= state.buffer.length && !forced)
362
+ return false; // run could still grow
363
+ if (count >= cb.backtick_count) {
364
+ let m = k;
365
+ while (m < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(m))) {
366
+ m++;
367
+ }
368
+ if (m >= state.buffer.length && !forced)
369
+ return false; // trailing content could disqualify
370
+ if (m >= state.buffer.length || state.buffer.charCodeAt(m) === NEWLINE) {
371
+ emit(state, { type: 'close', id: cb.id, end: offset(state, m) });
372
+ state.codeblock = null;
373
+ state.pos = m; // at the closer's newline (or EOF)
374
+ state.column = m - line_start;
375
+ return true;
376
+ }
377
+ }
378
+ // not a closer — the line is content
379
+ }
380
+ // content line — emit it whole once its newline arrives (or at EOF when forced)
381
+ const nl = state.buffer.indexOf('\n', j);
382
+ if (nl === -1 && !forced)
383
+ return false; // hold the partial line
384
+ const line_end = nl === -1 ? state.buffer.length : nl;
385
+ const strip = line_start + Math.min(cb.fence_indent, line_indent);
386
+ const content = state.buffer.slice(strip, line_end);
387
+ emit_codeblock_text(state, cb, cb.text_id !== null ? '\n' + content : content, strip);
388
+ state.pos = nl === -1 ? line_end : line_end + 1;
389
+ state.column = 0;
390
+ state.prev_char = NEWLINE;
391
+ }
392
+ return true;
393
+ };
394
+ /**
395
+ * Check if the buffer at current position has a codeblock closing fence —
396
+ * a run of at least `backtick_count` backticks with only whitespace to end
397
+ * of line. Returns the position after the fence (including trailing newline)
398
+ * on success, `-1` when definitely not a fence, or `-2` when more input is
399
+ * needed to decide.
400
+ *
401
+ * When `forced` is true, EOF after the fence counts as a valid terminator
402
+ * (matches `mdz_parse`'s handling of code blocks at end of input).
403
+ *
404
+ * @nodocs
405
+ */
406
+ export const match_codeblock_close = (state, backtick_count, forced = false) => {
407
+ let i = state.pos;
408
+ // count backticks
409
+ let count = 0;
410
+ while (i < state.buffer.length && state.buffer.charCodeAt(i) === BACKTICK) {
411
+ count++;
412
+ i++;
413
+ }
414
+ if (count < backtick_count) {
415
+ // buffer ended mid-backtick-sequence — could still grow into a fence
416
+ if (!forced && i >= state.buffer.length)
417
+ return -2;
418
+ return -1;
419
+ }
420
+ // trailing whitespace allowed
421
+ while (i < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(i))) {
422
+ i++;
423
+ }
424
+ // must be followed by newline or EOF
425
+ if (i < state.buffer.length && state.buffer.charCodeAt(i) === NEWLINE) {
426
+ return i + 1;
427
+ }
428
+ if (i >= state.buffer.length) {
429
+ // in forced mode, EOF terminates the fence
430
+ if (forced)
431
+ return i;
432
+ // enough backticks but buffer ended — the run could still grow, or
433
+ // whitespace/newline/disqualifying content could follow
434
+ return -2;
435
+ }
436
+ return -1;
437
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Inline-formatting handlers (bold, italic, strikethrough, inline code)
3
+ * for the streaming mdz parser.
4
+ *
5
+ * @module
6
+ */
7
+ import { type MdzStreamParserState } from './mdz_stream_parser_state.js';
8
+ /**
9
+ * Try to open a doubled-delimiter inline (`**` bold, `~~` strikethrough) —
10
+ * the streaming analogue of the lexer's `#tokenize_double_delimiter`. No
11
+ * word-boundary checks, matching the one-shot parse: doubled delimiters are
12
+ * unambiguous enough to pair anywhere, and the first closer wins.
13
+ *
14
+ * @nodocs
15
+ */
16
+ export declare const try_double_delimiter: (state: MdzStreamParserState, delimiter: "**" | "~~", node_type: "Bold" | "Strikethrough", forced?: boolean) => boolean;
17
+ /**
18
+ * Try to open an italic span (`_`) — the streaming analogue of the lexer's
19
+ * `#tokenize_italic`.
20
+ *
21
+ * @nodocs
22
+ */
23
+ export declare const try_italic: (state: MdzStreamParserState, forced?: boolean) => boolean;
24
+ /**
25
+ * Whether the char after a closing italic candidate permits the close (only
26
+ * `_` has close-boundary rules — `**`/`~~` close anywhere). Word chars and
27
+ * `_` itself (self-opaque — `_x__` never closes on the first `_` of a run)
28
+ * fail the boundary.
29
+ *
30
+ * At end of buffer the boundary is treated as valid — only reachable in
31
+ * forced mode, where end-of-input is a true boundary; while streaming,
32
+ * `process_inline` holds the closer decision until the next char arrives.
33
+ *
34
+ * @nodocs
35
+ */
36
+ export declare const check_close_word_boundary: (state: MdzStreamParserState) => boolean;
37
+ /**
38
+ * Close an open delimiter-paired container (`Bold`, `Italic`, `Strikethrough`)
39
+ * at the given stack index, consuming the closing delimiter — read from the
40
+ * entry, so `**`/`~~` advance two chars and `_` one. The caller in
41
+ * `process_inline` already located the open via `find_open`, so we trust the
42
+ * index rather than re-walking the stack.
43
+ *
44
+ * @nodocs
45
+ */
46
+ export declare const close_delimiter: (state: MdzStreamParserState, stack_idx: number) => boolean;
47
+ /**
48
+ * Scan for the first potential closing `_` and check its word boundary (only
49
+ * italic has closer-boundary rules). Returns a tri-state:
50
+ * - `'confirmed'`: closer found in buffer with valid word boundary
51
+ * - `'rejected'`: closer found but with invalid word boundary (greedy first-match fails)
52
+ * - `'pending'`: no closer in buffer yet, or can't determine boundary
53
+ *
54
+ * The boundary check is self-opaque: a candidate followed by `_` itself is
55
+ * `'rejected'`, so underscore runs (`__init__`) degrade to literal text.
56
+ * While streaming, `'pending'` opens optimistically (the revert machinery
57
+ * corrects later); in forced mode it degrades to text.
58
+ *
59
+ * The delimiter search uses raw `indexOf` and stays amortized-linear without
60
+ * a memo: candidates are occurrences of the searched character itself, so a
61
+ * scan ends either at the next candidate (consecutive scans cover disjoint
62
+ * ranges) or at the end of the buffer with no candidates left to rescan it.
63
+ * The run-boundary probe (paragraph breaks, and list structure inside open
64
+ * lists) is bounded to the same window — only a boundary *before* the found
65
+ * delimiter matters — inheriting that amortization; an unbounded probe would
66
+ * rescan to EOF per candidate on single-paragraph input, going quadratic.
67
+ *
68
+ * @nodocs
69
+ */
70
+ export declare const scan_closer_boundary: (state: MdzStreamParserState, forced?: boolean) => "confirmed" | "rejected" | "pending";
71
+ /** @nodocs */
72
+ export declare const try_code: (state: MdzStreamParserState, forced?: boolean) => boolean;
73
+ /**
74
+ * Compute search limit for inline code scanning.
75
+ * Finds the closest closing delimiter for any open formatting on the stack.
76
+ * This prevents backtick scanning from crossing bold/italic/etc boundaries.
77
+ *
78
+ * @nodocs
79
+ */
80
+ export declare const code_search_limit: (state: MdzStreamParserState, from: number) => number;
81
+ /**
82
+ * Check if any inline formatting container (Bold, Italic, Strikethrough, Link)
83
+ * is on the stack above the current block boundary.
84
+ * Used to decide whether backtick can go optimistic — when formatting is open,
85
+ * the code span might cross the formatting boundary, so we hold instead.
86
+ *
87
+ * @nodocs
88
+ */
89
+ export declare const has_inline_formatting_on_stack: (state: MdzStreamParserState) => boolean;
90
+ /**
91
+ * Process content inside an optimistic inline Code container.
92
+ * Scans raw text (no inline formatting) until closing backtick or newline.
93
+ * Backtick closes (or reverts if empty). Newline reverts (code can't span lines).
94
+ *
95
+ * @nodocs
96
+ */
97
+ export declare const process_code_content: (state: MdzStreamParserState) => void;
98
+ //# sourceMappingURL=mdz_stream_parser_inline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_stream_parser_inline.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_inline.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,EACN,KAAK,oBAAoB,EAYzB,MAAM,8BAA8B,CAAC;AA6GtC;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAChC,OAAO,oBAAoB,EAC3B,WAAW,IAAI,GAAG,IAAI,EACtB,WAAW,MAAM,GAAG,eAAe,EACnC,gBAAc,KACZ,OA2BF,CAAC;AAIF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,oBAAoB,EAAE,gBAAc,KAAG,OAyCxE,CAAC;AAIF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,oBAAoB,KAAG,OAOvE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,oBAAoB,EAAE,WAAW,MAAM,KAAG,OAuBhF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,oBAAoB,GAChC,OAAO,oBAAoB,EAC3B,gBAAc,KACZ,WAAW,GAAG,UAAU,GAAG,SAmB7B,CAAC;AAIF,cAAc;AACd,eAAO,MAAM,QAAQ,GAAI,OAAO,oBAAoB,EAAE,gBAAc,KAAG,OAoFtE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,oBAAoB,EAAE,MAAM,MAAM,KAAG,MAyB7E,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAAI,OAAO,oBAAoB,KAAG,OAO5E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,oBAAoB,KAAG,IA2ClE,CAAC"}