@fuzdev/mdz 0.1.0 → 0.2.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 (60) hide show
  1. package/README.md +4 -0
  2. package/dist/MdzNodeView.svelte +30 -1
  3. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  4. package/dist/MdzStreamNodeView.svelte +31 -0
  5. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
  6. package/dist/context_helpers.d.ts +1 -1
  7. package/dist/mdz.d.ts +53 -2
  8. package/dist/mdz.d.ts.map +1 -1
  9. package/dist/mdz.js +29 -1
  10. package/dist/mdz_helpers.d.ts +35 -1
  11. package/dist/mdz_helpers.d.ts.map +1 -1
  12. package/dist/mdz_helpers.js +120 -0
  13. package/dist/mdz_lexer.d.ts +35 -1
  14. package/dist/mdz_lexer.d.ts.map +1 -1
  15. package/dist/mdz_lexer.js +211 -2
  16. package/dist/mdz_opcodes.d.ts +6 -1
  17. package/dist/mdz_opcodes.d.ts.map +1 -1
  18. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -1
  19. package/dist/mdz_opcodes_to_nodes.js +26 -0
  20. package/dist/mdz_stream_parser.d.ts.map +1 -1
  21. package/dist/mdz_stream_parser.js +20 -1
  22. package/dist/mdz_stream_parser_list.d.ts.map +1 -1
  23. package/dist/mdz_stream_parser_list.js +22 -1
  24. package/dist/mdz_stream_parser_state.d.ts +12 -0
  25. package/dist/mdz_stream_parser_state.d.ts.map +1 -1
  26. package/dist/mdz_stream_parser_state.js +1 -0
  27. package/dist/mdz_stream_parser_table.d.ts +72 -0
  28. package/dist/mdz_stream_parser_table.d.ts.map +1 -0
  29. package/dist/mdz_stream_parser_table.js +351 -0
  30. package/dist/mdz_stream_state.svelte.d.ts +5 -0
  31. package/dist/mdz_stream_state.svelte.d.ts.map +1 -1
  32. package/dist/mdz_stream_state.svelte.js +8 -0
  33. package/dist/mdz_to_svelte.d.ts.map +1 -1
  34. package/dist/mdz_to_svelte.js +37 -3
  35. package/dist/mdz_token_parser.d.ts +16 -0
  36. package/dist/mdz_token_parser.d.ts.map +1 -1
  37. package/dist/mdz_token_parser.js +101 -2
  38. package/dist/svelte_preprocess_mdz.js +2 -2
  39. package/package.json +8 -8
  40. package/src/lib/context_helpers.ts +1 -1
  41. package/src/lib/mdz.ts +70 -3
  42. package/src/lib/mdz_contexts.ts +1 -1
  43. package/src/lib/mdz_helpers.ts +125 -1
  44. package/src/lib/mdz_lexer.ts +262 -3
  45. package/src/lib/mdz_opcodes.ts +10 -1
  46. package/src/lib/mdz_opcodes_to_nodes.ts +37 -3
  47. package/src/lib/mdz_stream_parser.ts +26 -9
  48. package/src/lib/mdz_stream_parser_block.ts +2 -2
  49. package/src/lib/mdz_stream_parser_inline.ts +4 -4
  50. package/src/lib/mdz_stream_parser_link.ts +3 -3
  51. package/src/lib/mdz_stream_parser_list.ts +24 -2
  52. package/src/lib/mdz_stream_parser_state.ts +12 -2
  53. package/src/lib/mdz_stream_parser_table.ts +409 -0
  54. package/src/lib/mdz_stream_parser_text.ts +2 -2
  55. package/src/lib/mdz_stream_parser_url.ts +2 -2
  56. package/src/lib/mdz_stream_state.svelte.ts +8 -1
  57. package/src/lib/mdz_to_svelte.ts +39 -5
  58. package/src/lib/mdz_token_parser.ts +122 -5
  59. package/src/lib/svelte_preprocess_mdz.ts +4 -4
  60. package/src/lib/tsdoc_mdz.ts +1 -1
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Table handlers for the streaming mdz parser.
3
+ *
4
+ * A table is recognized at a column-0 pipe row whose next line is a valid
5
+ * delimiter row with a matching column count — a bounded one-line lookahead:
6
+ * the header row holds (`need_more`) until its delimiter line arrives, then the
7
+ * whole header emits at once. Body rows then stream one per line. Because a row
8
+ * is always fully buffered before it emits (no within-row streaming), each
9
+ * cell's inline content is parsed with the sync reference (`MdzTableCellParser`,
10
+ * one `MdzLexer`/`MdzTokenParser` shared across the row's cells) and replayed
11
+ * as opcodes — guaranteeing sync↔stream parity by construction.
12
+ *
13
+ * @module
14
+ */
15
+ import { DEV } from 'esm-env';
16
+ import { NEWLINE, PIPE, is_line_whitespace, mdz_split_table_row, mdz_parse_table_delimiter, } from './mdz_helpers.js';
17
+ import { MdzTableCellParser } from './mdz.js';
18
+ import { alloc_id, close_paragraph, emit, flush_text, offset, pop_stack_entry, push_stack_entry, } from './mdz_stream_parser_state.js';
19
+ /**
20
+ * Try to open a table at a column-0 pipe row. Returns `need_more` until both
21
+ * the header line and its delimiter line are buffered (or, in forced mode,
22
+ * resolvable), `not_match` when the two lines aren't a valid header+delimiter
23
+ * pair, and `consumed` once the table opens (closing any open paragraph first,
24
+ * emitting the header row, and leaving `state.pos` at the first body line).
25
+ *
26
+ * @nodocs
27
+ */
28
+ export const try_table_start = (state, forced) => {
29
+ const start = state.pos;
30
+ const header_end = state.buffer.indexOf('\n', start);
31
+ // a table needs a header line plus a delimiter line — without the header's
32
+ // newline there's no room for a delimiter (at EOF it can never be a table)
33
+ if (header_end === -1)
34
+ return forced ? 'not_match' : 'need_more';
35
+ const header_cells = mdz_split_table_row(state.buffer, start, header_end);
36
+ if (header_cells === null)
37
+ return 'not_match';
38
+ const delim_start = header_end + 1;
39
+ let delim_end = state.buffer.indexOf('\n', delim_start);
40
+ if (delim_end === -1) {
41
+ if (!forced)
42
+ return 'need_more'; // delimiter line not complete yet
43
+ delim_end = state.buffer.length;
44
+ }
45
+ const align = mdz_parse_table_delimiter(state.buffer, delim_start, delim_end);
46
+ if (align === null || align.length !== header_cells.length)
47
+ return 'not_match';
48
+ // committed — the delimiter row is structural, never emitted
49
+ flush_text(state);
50
+ close_paragraph(state);
51
+ const table_id = alloc_id(state);
52
+ const table_start = offset(state, start);
53
+ emit(state, { type: 'open', id: table_id, node_type: 'Table', start: table_start, align });
54
+ push_stack_entry(state, table_id, 'Table', table_start);
55
+ state.table = { id: table_id, item_indent: null };
56
+ emit_table_row(state, header_cells, true, start, header_end);
57
+ // skip past the delimiter row to the first potential body line
58
+ state.pos = delim_end === state.buffer.length ? delim_end : delim_end + 1;
59
+ state.column = 0;
60
+ state.prev_char = NEWLINE;
61
+ return 'consumed';
62
+ };
63
+ /**
64
+ * Process the line at `state.pos` while a table is open: emit it as a body row,
65
+ * or close the table when it isn't a valid pipe row. Returns false (need more
66
+ * input) only when a pipe-row line is started but not yet complete. A non-pipe
67
+ * line (including a blank line) ends the table and is left for normal
68
+ * processing.
69
+ *
70
+ * @nodocs
71
+ */
72
+ export const process_table_line = (state, forced) => {
73
+ const item_indent = state.table.item_indent;
74
+ if (item_indent !== null) {
75
+ return process_table_line_in_item(state, forced, item_indent);
76
+ }
77
+ if (state.pos >= state.buffer.length) {
78
+ // the table may continue with more rows in a later chunk
79
+ if (forced) {
80
+ close_table(state);
81
+ return true;
82
+ }
83
+ return false;
84
+ }
85
+ const row_start = state.pos;
86
+ // a body row must start with `|` at column 0; anything else ends the table
87
+ // (decided without waiting for the line's newline)
88
+ if (state.buffer.charCodeAt(row_start) !== PIPE) {
89
+ close_table(state);
90
+ return true;
91
+ }
92
+ let row_end = state.buffer.indexOf('\n', row_start);
93
+ if (row_end === -1) {
94
+ if (!forced)
95
+ return false; // pipe-row line not complete — hold
96
+ row_end = state.buffer.length;
97
+ }
98
+ const cells = mdz_split_table_row(state.buffer, row_start, row_end);
99
+ if (cells === null) {
100
+ // starts with `|` but isn't a valid row (e.g. no trailing pipe) — table ends
101
+ close_table(state);
102
+ return true;
103
+ }
104
+ emit_table_row(state, cells, false, row_start, row_end);
105
+ state.pos = row_end === state.buffer.length ? row_end : row_end + 1;
106
+ state.column = 0;
107
+ state.prev_char = NEWLINE;
108
+ return true;
109
+ };
110
+ /**
111
+ * Process body rows of an in-item table. Unlike the top-level path this keeps
112
+ * `state.pos` on the newline that ends each row — a row boundary the buffer
113
+ * never compacts past, since it's the active cursor. So when a dedent (to the
114
+ * item's `marker_indent` or shallower), a blank line, or a non-pipe line ends
115
+ * the table, `close_table` leaves the cursor on that newline and the list's
116
+ * newline handler re-classifies the following line — the same control-return
117
+ * shape as the in-item codeblock, robust across chunk boundaries.
118
+ */
119
+ const process_table_line_in_item = (state, forced, item_indent) => {
120
+ if (state.pos >= state.buffer.length) {
121
+ if (forced) {
122
+ close_table(state);
123
+ return true;
124
+ }
125
+ return false;
126
+ }
127
+ // `state.pos` sits on the newline ending the previous row (or the delimiter);
128
+ // the candidate next row is the line after it
129
+ const line_start = state.pos + 1;
130
+ let j = line_start;
131
+ while (j < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(j))) {
132
+ j++;
133
+ }
134
+ if (j >= state.buffer.length) {
135
+ if (forced) {
136
+ close_table(state);
137
+ return true;
138
+ }
139
+ return false; // indent-only / incomplete line — hold
140
+ }
141
+ if (j - line_start <= item_indent || state.buffer.charCodeAt(j) !== PIPE) {
142
+ // a dedent to the marker (or shallower), a blank line, or a non-pipe line
143
+ // ends the table; the cursor stays on the row-boundary newline so the
144
+ // list resumes via its newline handler
145
+ close_table(state);
146
+ return true;
147
+ }
148
+ let row_end = state.buffer.indexOf('\n', j);
149
+ if (row_end === -1) {
150
+ if (!forced)
151
+ return false; // pipe-row line not complete — hold
152
+ row_end = state.buffer.length;
153
+ }
154
+ const cells = mdz_split_table_row(state.buffer, j, row_end);
155
+ if (cells === null) {
156
+ close_table(state);
157
+ return true;
158
+ }
159
+ emit_table_row(state, cells, false, j, row_end);
160
+ state.pos = row_end; // stay on this row's terminating newline (or EOF)
161
+ return true;
162
+ };
163
+ /**
164
+ * Close the open table.
165
+ *
166
+ * @nodocs
167
+ */
168
+ export const close_table = (state) => {
169
+ if (!state.table)
170
+ return;
171
+ const { item_indent } = state.table;
172
+ const entry = pop_stack_entry(state);
173
+ emit(state, { type: 'close', id: entry.id, end: offset(state) });
174
+ state.table = null;
175
+ state.active_text_id = null;
176
+ if (item_indent === null) {
177
+ // top-level: the trailing newline + any blank lines absorb at the loop top
178
+ state.skip_blank_lines = true;
179
+ }
180
+ // in-item: the cursor is left on a row-boundary newline (or EOF). The list's
181
+ // newline handler re-classifies the following line; at EOF, `finish` closes
182
+ // the list. No skip_blank_lines — the list owns blank-line containment.
183
+ };
184
+ /**
185
+ * Recognize a table opening as a block child of a list item: a pipe row at
186
+ * `header_first` (the line's indent already skipped) whose next line is a
187
+ * delimiter row, both indented past `marker_indent` (so both stay inside the
188
+ * item). Returns the parsed head, `'pending'` when the two-line lookahead isn't
189
+ * fully buffered, or `null` when it isn't a table.
190
+ *
191
+ * @nodocs
192
+ */
193
+ export const match_table_in_item_head = (state, header_first, marker_indent, forced) => {
194
+ const { buffer } = state;
195
+ const header_end = buffer.indexOf('\n', header_first);
196
+ if (header_end === -1)
197
+ return forced ? null : 'pending'; // header needs its newline + a delimiter beneath
198
+ const header_cells = mdz_split_table_row(buffer, header_first, header_end);
199
+ if (header_cells === null)
200
+ return null;
201
+ const delim_line_start = header_end + 1;
202
+ let dj = delim_line_start;
203
+ while (dj < buffer.length && is_line_whitespace(buffer.charCodeAt(dj)))
204
+ dj++;
205
+ if (dj >= buffer.length)
206
+ return forced ? null : 'pending'; // delimiter line not buffered yet
207
+ if (dj - delim_line_start <= marker_indent)
208
+ return null; // delimiter dedented out of the item
209
+ let delim_end = buffer.indexOf('\n', dj);
210
+ if (delim_end === -1) {
211
+ if (!forced)
212
+ return 'pending'; // delimiter line incomplete
213
+ delim_end = buffer.length;
214
+ }
215
+ const align = mdz_parse_table_delimiter(buffer, dj, delim_end);
216
+ if (align === null || align.length !== header_cells.length)
217
+ return null;
218
+ return { header_cells, header_end, align, delim_end };
219
+ };
220
+ /**
221
+ * Open a table as a block child of a list item at `marker_indent`, given a
222
+ * `head` from `match_table_in_item_head`. Emits the `Table` open + header row
223
+ * and enters table mode (with `item_indent`), leaving `state.pos` at the first
224
+ * body line. The caller closes the item's run and pops to the attach level first.
225
+ *
226
+ * @nodocs
227
+ */
228
+ export const open_table_in_item = (state, header_first, marker_indent, head) => {
229
+ const table_id = alloc_id(state);
230
+ const table_start = offset(state, header_first);
231
+ emit(state, {
232
+ type: 'open',
233
+ id: table_id,
234
+ node_type: 'Table',
235
+ start: table_start,
236
+ align: head.align,
237
+ });
238
+ push_stack_entry(state, table_id, 'Table', table_start);
239
+ state.table = { id: table_id, item_indent: marker_indent };
240
+ emit_table_row(state, head.header_cells, true, header_first, head.header_end);
241
+ // leave the cursor on the delimiter's newline (or EOF) — the in-item body-row
242
+ // handler reads from the line after it and hands back on this same convention
243
+ state.pos = head.delim_end;
244
+ state.column = 0;
245
+ state.prev_char = NEWLINE;
246
+ };
247
+ /** Emit one table row: open, each cell, close. */
248
+ const emit_table_row = (state, cells, header, row_start, row_end) => {
249
+ const row_id = alloc_id(state);
250
+ const row_off = offset(state, row_start);
251
+ emit(state, { type: 'open', id: row_id, node_type: 'TableRow', start: row_off, header });
252
+ push_stack_entry(state, row_id, 'TableRow', row_off);
253
+ // one lexer/parser shared across the row's cells (all index into the same
254
+ // buffer) rather than allocated per cell
255
+ const cell_parser = new MdzTableCellParser(state.buffer);
256
+ for (const cell of cells) {
257
+ emit_table_cell(state, cell_parser, cell.start, cell.end);
258
+ }
259
+ pop_stack_entry(state);
260
+ emit(state, { type: 'close', id: row_id, end: offset(state, row_end) });
261
+ state.active_text_id = null;
262
+ };
263
+ /**
264
+ * Emit one cell: open, its inline content (parsed with the sync reference over
265
+ * the fully-buffered span via the row's shared `cell_parser`), close.
266
+ */
267
+ const emit_table_cell = (state, cell_parser, cell_start, cell_end) => {
268
+ const cell_id = alloc_id(state);
269
+ const cell_off = offset(state, cell_start);
270
+ emit(state, { type: 'open', id: cell_id, node_type: 'TableCell', start: cell_off });
271
+ push_stack_entry(state, cell_id, 'TableCell', cell_off);
272
+ emit_inline_nodes(state, cell_parser.parse(cell_start, cell_end));
273
+ pop_stack_entry(state);
274
+ emit(state, { type: 'close', id: cell_id, end: offset(state, cell_end) });
275
+ state.active_text_id = null;
276
+ };
277
+ /**
278
+ * Replay sync-parsed inline nodes as opcodes. Cell content is inline-only;
279
+ * positions are local to `state.buffer` and lifted to global via `offset`.
280
+ */
281
+ const emit_inline_nodes = (state, nodes) => {
282
+ for (const node of nodes) {
283
+ switch (node.type) {
284
+ case 'Text':
285
+ emit(state, {
286
+ type: 'text',
287
+ id: alloc_id(state),
288
+ content: node.content,
289
+ text_type: 'Text',
290
+ start: offset(state, node.start),
291
+ end: offset(state, node.end),
292
+ });
293
+ break;
294
+ case 'Code':
295
+ emit(state, {
296
+ type: 'text',
297
+ id: alloc_id(state),
298
+ content: node.content,
299
+ text_type: 'Code',
300
+ start: offset(state, node.start),
301
+ end: offset(state, node.end),
302
+ });
303
+ break;
304
+ case 'Bold':
305
+ case 'Italic':
306
+ case 'Strikethrough': {
307
+ const id = alloc_id(state);
308
+ const s = offset(state, node.start);
309
+ emit(state, { type: 'open', id, node_type: node.type, start: s });
310
+ push_stack_entry(state, id, node.type, s);
311
+ emit_inline_nodes(state, node.children);
312
+ pop_stack_entry(state);
313
+ emit(state, { type: 'close', id, end: offset(state, node.end) });
314
+ break;
315
+ }
316
+ case 'Link': {
317
+ const id = alloc_id(state);
318
+ const s = offset(state, node.start);
319
+ emit(state, { type: 'open', id, node_type: 'Link', start: s });
320
+ push_stack_entry(state, id, 'Link', s);
321
+ emit_inline_nodes(state, node.children);
322
+ pop_stack_entry(state);
323
+ emit(state, {
324
+ type: 'close',
325
+ id,
326
+ end: offset(state, node.end),
327
+ reference: node.reference,
328
+ link_type: node.link_type,
329
+ });
330
+ break;
331
+ }
332
+ case 'Element':
333
+ case 'Component': {
334
+ const id = alloc_id(state);
335
+ const s = offset(state, node.start);
336
+ emit(state, { type: 'open', id, node_type: node.type, start: s, name: node.name });
337
+ push_stack_entry(state, id, node.type, s, '', node.name);
338
+ emit_inline_nodes(state, node.children);
339
+ pop_stack_entry(state);
340
+ emit(state, { type: 'close', id, end: offset(state, node.end) });
341
+ break;
342
+ }
343
+ // cells are inline-only (lexed via `lex_table_cell`); a block node here
344
+ // would mean a sync/stream grammar drift — fail loud in dev
345
+ default:
346
+ if (DEV) {
347
+ throw new Error(`mdz table cell: unexpected inline node type '${node.type}'`);
348
+ }
349
+ }
350
+ }
351
+ };
@@ -8,6 +8,7 @@
8
8
  * @module
9
9
  */
10
10
  import type { MdzNodeId, MdzNodeType, MdzOpcode } from './mdz_opcodes.js';
11
+ import type { MdzTableAlign } from './mdz.js';
11
12
  /**
12
13
  * A reactive node in the stream renderer tree.
13
14
  * Fields are `$state` so Svelte re-renders only what changes.
@@ -26,6 +27,10 @@ export declare class MdzStreamNode {
26
27
  ordered?: boolean;
27
28
  start_number?: number;
28
29
  number?: number;
30
+ /** Per-column alignment (Table nodes). */
31
+ align?: Array<MdzTableAlign>;
32
+ /** Whether this is the header row (TableRow nodes). */
33
+ header?: boolean;
29
34
  constructor(id: MdzNodeId, type: MdzNodeType);
30
35
  }
31
36
  /**
@@ -1 +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"}
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;AACxE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,UAAU,CAAC;AAE5C;;;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;IAChB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAC7B,uDAAuD;IACvD,MAAM,CAAC,EAAE,OAAO,CAAC;gBAEL,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;IA6R9B;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI;CAK5C"}
@@ -27,6 +27,10 @@ export class MdzStreamNode {
27
27
  ordered;
28
28
  start_number;
29
29
  number;
30
+ /** Per-column alignment (Table nodes). */
31
+ align;
32
+ /** Whether this is the header row (TableRow nodes). */
33
+ header;
30
34
  constructor(id, type) {
31
35
  this.id = id;
32
36
  this.type = type;
@@ -96,6 +100,10 @@ export class MdzStreamState {
96
100
  node.start_number = opcode.start_number;
97
101
  if (opcode.number !== undefined)
98
102
  node.number = opcode.number;
103
+ if (opcode.align !== undefined)
104
+ node.align = opcode.align;
105
+ if (opcode.header !== undefined)
106
+ node.header = opcode.header;
99
107
  this.#attach(node);
100
108
  this.#stack.push(opcode.id);
101
109
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"mdz_to_svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_to_svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAGtC;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IAEf,yDAAyD;IACzD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;KAAC,CAAC,CAAC;IAEhE,yEAAyE;IACzE,qBAAqB,EAAE,OAAO,CAAC;CAC/B;AAMD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEnC;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACzB,OAAO,KAAK,CAAC,OAAO,CAAC,EACrB,SAAS,kBAAkB,KACzB,iBA4HF,CAAC"}
1
+ {"version":3,"file":"mdz_to_svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_to_svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAC,OAAO,EAAkB,MAAM,UAAU,CAAC;AAGvD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IAEf,yDAAyD;IACzD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;KAAC,CAAC,CAAC;IAEhE,yEAAyE;IACzE,qBAAqB,EAAE,OAAO,CAAC;CAC/B;AAMD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEnC;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACzB,OAAO,KAAK,CAAC,OAAO,CAAC,EACrB,SAAS,kBAAkB,KACzB,iBA8JF,CAAC"}
@@ -7,9 +7,9 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- import { UnreachableError } from '@fuzdev/fuz_util/error.js';
11
- import { escape_svelte_text } from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
12
- import { escape_js_string } from '@fuzdev/fuz_util/string.js';
10
+ import { UnreachableError } from '@fuzdev/fuz_util/error.ts';
11
+ import { escape_svelte_text } from '@fuzdev/fuz_util/svelte_preprocess_helpers.ts';
12
+ import { escape_js_string } from '@fuzdev/fuz_util/string.ts';
13
13
  import { mdz_resolve_relative_path, mdz_is_void_element } from './mdz_helpers.js';
14
14
  /** Derives an import's local identifier from a `.svelte` import path's basename. */
15
15
  const component_local_name = (import_path) => import_path.slice(import_path.lastIndexOf('/') + 1).replace(/\.svelte$/, '');
@@ -90,6 +90,40 @@ export const mdz_to_svelte = (nodes, options) => {
90
90
  return `<li>${render_nodes(node.children)}</li>`;
91
91
  case 'Blockquote':
92
92
  return `<blockquote>${render_nodes(node.children)}</blockquote>`;
93
+ case 'Table': {
94
+ const { align } = node;
95
+ const render_row = (row, tag) => {
96
+ let cells = '';
97
+ // normalize to the column count: pad short rows, ignore extra cells
98
+ for (let i = 0; i < align.length; i++) {
99
+ const a = align[i];
100
+ const style = a ? ` style="text-align:${a}"` : '';
101
+ const cell = row.children[i];
102
+ cells += `<${tag}${style}>${cell ? render_nodes(cell.children) : ''}</${tag}>`;
103
+ }
104
+ return `<tr>${cells}</tr>`;
105
+ };
106
+ let out = '<table>';
107
+ const header_rows = node.children.filter((r) => r.header);
108
+ const body_rows = node.children.filter((r) => !r.header);
109
+ if (header_rows.length > 0) {
110
+ out += '<thead>';
111
+ for (const row of header_rows)
112
+ out += render_row(row, 'th');
113
+ out += '</thead>';
114
+ }
115
+ if (body_rows.length > 0) {
116
+ out += '<tbody>';
117
+ for (const row of body_rows)
118
+ out += render_row(row, 'td');
119
+ out += '</tbody>';
120
+ }
121
+ return out + '</table>';
122
+ }
123
+ case 'TableRow':
124
+ case 'TableCell':
125
+ // rendered within their `Table`; never reached through `render_nodes`
126
+ throw new Error(`mdz: ${node.type} must render within a Table`);
93
127
  case 'Hr':
94
128
  return '<hr />';
95
129
  case 'Heading': {
@@ -15,6 +15,22 @@ import { type MdzToken } from './mdz_lexer.js';
15
15
  export declare class MdzTokenParser {
16
16
  #private;
17
17
  constructor(tokens: Array<MdzToken>);
18
+ /**
19
+ * Rebind to a new token stream and rewind, so one parser instance can be
20
+ * reused across inputs — the streaming table path reuses one across a row's
21
+ * cells via `MdzTableCellParser`.
22
+ *
23
+ * @nodocs
24
+ */
25
+ reset(tokens: Array<MdzToken>): void;
18
26
  parse(): Array<MdzNode>;
27
+ /**
28
+ * Parse the entire token stream as a single inline run — used for table
29
+ * cells, whose tokens come from `MdzLexer.lex_table_cell`. Adjacent text
30
+ * merges as it accumulates, matching `#parse_table_cell`.
31
+ *
32
+ * @nodocs
33
+ */
34
+ parse_inline_run(): Array<MdzNode>;
19
35
  }
20
36
  //# sourceMappingURL=mdz_token_parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mdz_token_parser.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_token_parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACX,OAAO,EAYP,MAAM,UAAU,CAAC;AAQlB,OAAO,EACN,KAAK,QAAQ,EASb,MAAM,gBAAgB,CAAC;AAExB;;;GAGG;AACH,qBAAa,cAAc;;gBAId,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;IAInC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC;CAyevB"}
1
+ {"version":3,"file":"mdz_token_parser.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_token_parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACX,OAAO,EAeP,MAAM,UAAU,CAAC;AAQlB,OAAO,EACN,KAAK,QAAQ,EAYb,MAAM,gBAAgB,CAAC;AAExB;;;GAGG;AACH,qBAAa,cAAc;;gBAId,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;IAInC;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI;IAKpC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC;IAIvB;;;;;;OAMG;IACH,gBAAgB,IAAI,KAAK,CAAC,OAAO,CAAC;CAikBlC"}
@@ -18,9 +18,36 @@ export class MdzTokenParser {
18
18
  constructor(tokens) {
19
19
  this.#tokens = tokens;
20
20
  }
21
+ /**
22
+ * Rebind to a new token stream and rewind, so one parser instance can be
23
+ * reused across inputs — the streaming table path reuses one across a row's
24
+ * cells via `MdzTableCellParser`.
25
+ *
26
+ * @nodocs
27
+ */
28
+ reset(tokens) {
29
+ this.#tokens = tokens;
30
+ this.#index = 0;
31
+ }
21
32
  parse() {
22
33
  return this.#parse_block_stream(false);
23
34
  }
35
+ /**
36
+ * Parse the entire token stream as a single inline run — used for table
37
+ * cells, whose tokens come from `MdzLexer.lex_table_cell`. Adjacent text
38
+ * merges as it accumulates, matching `#parse_table_cell`.
39
+ *
40
+ * @nodocs
41
+ */
42
+ parse_inline_run() {
43
+ const children = [];
44
+ while (this.#index < this.#tokens.length) {
45
+ const node = this.#parse_inline();
46
+ if (node)
47
+ mdz_push_merging_text(children, node);
48
+ }
49
+ return children;
50
+ }
24
51
  /**
25
52
  * Parse a run of block-level tokens into nodes — the document root, or a
26
53
  * blockquote's contents (a quote's content is a mini-document, so both
@@ -80,6 +107,13 @@ export class MdzTokenParser {
80
107
  nodes.push(this.#parse_blockquote());
81
108
  continue;
82
109
  }
110
+ if (token.type === 'table_open') {
111
+ const flushed = this.#flush_paragraph(paragraph_children, true);
112
+ if (flushed)
113
+ nodes.push(flushed);
114
+ nodes.push(this.#parse_table());
115
+ continue;
116
+ }
83
117
  if (token.type === 'paragraph_break') {
84
118
  const flushed = this.#flush_paragraph(paragraph_children, true);
85
119
  if (flushed)
@@ -107,6 +141,63 @@ export class MdzTokenParser {
107
141
  const end = children.length > 0 ? children[children.length - 1].end : open.end;
108
142
  return { type: 'Blockquote', children, start: open.start, end };
109
143
  }
144
+ #parse_table() {
145
+ const open = this.#tokens[this.#index];
146
+ this.#index++;
147
+ const rows = [];
148
+ while (this.#index < this.#tokens.length) {
149
+ const t = this.#tokens[this.#index];
150
+ if (t.type === 'table_row_open') {
151
+ rows.push(this.#parse_table_row());
152
+ continue;
153
+ }
154
+ if (t.type === 'table_close') {
155
+ this.#index++;
156
+ break;
157
+ }
158
+ this.#index++; // skip anything unexpected (defensive)
159
+ }
160
+ const end = rows.length > 0 ? rows[rows.length - 1].end : open.end;
161
+ return { type: 'Table', align: open.align, children: rows, start: open.start, end };
162
+ }
163
+ #parse_table_row() {
164
+ const open = this.#tokens[this.#index];
165
+ const header = open.header;
166
+ this.#index++;
167
+ const cells = [];
168
+ while (this.#index < this.#tokens.length) {
169
+ const t = this.#tokens[this.#index];
170
+ if (t.type === 'table_cell_open') {
171
+ cells.push(this.#parse_table_cell());
172
+ continue;
173
+ }
174
+ if (t.type === 'table_row_close') {
175
+ this.#index++;
176
+ return { type: 'TableRow', header, children: cells, start: open.start, end: t.end };
177
+ }
178
+ this.#index++; // skip anything unexpected (defensive)
179
+ }
180
+ const end = cells.length > 0 ? cells[cells.length - 1].end : open.end;
181
+ return { type: 'TableRow', header, children: cells, start: open.start, end };
182
+ }
183
+ #parse_table_cell() {
184
+ const open = this.#tokens[this.#index];
185
+ const start = open.start;
186
+ this.#index++;
187
+ const children = [];
188
+ while (this.#index < this.#tokens.length) {
189
+ const t = this.#tokens[this.#index];
190
+ if (t.type === 'table_cell_close') {
191
+ this.#index++;
192
+ return { type: 'TableCell', children, start, end: t.end };
193
+ }
194
+ const node = this.#parse_inline();
195
+ if (node)
196
+ mdz_push_merging_text(children, node);
197
+ }
198
+ const end = children.length > 0 ? children[children.length - 1].end : open.end;
199
+ return { type: 'TableCell', children, start, end };
200
+ }
110
201
  #parse_heading() {
111
202
  const token = this.#tokens[this.#index];
112
203
  const start = token.start;
@@ -188,6 +279,12 @@ export class MdzTokenParser {
188
279
  children.push(this.#parse_blockquote());
189
280
  continue;
190
281
  }
282
+ if (t.type === 'table_open') {
283
+ flush();
284
+ in_paragraph = false;
285
+ children.push(this.#parse_table());
286
+ continue;
287
+ }
191
288
  if (t.type === 'codeblock') {
192
289
  flush();
193
290
  in_paragraph = false;
@@ -307,7 +404,8 @@ export class MdzTokenParser {
307
404
  t.type === 'codeblock' ||
308
405
  t.type === 'list_open' ||
309
406
  t.type === 'list_item_close' ||
310
- t.type === 'blockquote_open') {
407
+ t.type === 'blockquote_open' ||
408
+ t.type === 'table_open') {
311
409
  break;
312
410
  }
313
411
  const node = this.#parse_inline();
@@ -348,7 +446,8 @@ export class MdzTokenParser {
348
446
  t.type === 'codeblock' ||
349
447
  t.type === 'list_open' ||
350
448
  t.type === 'list_item_close' ||
351
- t.type === 'blockquote_open') {
449
+ t.type === 'blockquote_open' ||
450
+ t.type === 'table_open') {
352
451
  break;
353
452
  }
354
453
  const node = this.#parse_inline();
@@ -16,8 +16,8 @@
16
16
  import { parse } from 'svelte/compiler';
17
17
  import MagicString from 'magic-string';
18
18
  import { walk } from 'zimmerframe';
19
- import { should_exclude_path } from '@fuzdev/fuz_util/path.js';
20
- import { find_attribute, extract_static_string, try_extract_conditional_chain, build_static_bindings, resolve_component_names, has_identifier_in_tree, find_import_insert_position, generate_import_lines, remove_variable_declaration, remove_import_declaration, remove_import_specifier, handle_preprocess_error, } from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
19
+ import { should_exclude_path } from '@fuzdev/fuz_util/path.ts';
20
+ import { find_attribute, extract_static_string, try_extract_conditional_chain, build_static_bindings, resolve_component_names, has_identifier_in_tree, find_import_insert_position, generate_import_lines, remove_variable_declaration, remove_import_declaration, remove_import_specifier, handle_preprocess_error, } from '@fuzdev/fuz_util/svelte_preprocess_helpers.ts';
21
21
  import { mdz_parse } from './mdz.js';
22
22
  import { mdz_to_svelte } from './mdz_to_svelte.js';
23
23
  const PRECOMPILED_NAME = 'MdzPrecompiled';