@fuzdev/mdz 0.2.0 → 0.3.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 (56) hide show
  1. package/dist/Mdz.svelte +15 -7
  2. package/dist/Mdz.svelte.d.ts +9 -2
  3. package/dist/Mdz.svelte.d.ts.map +1 -1
  4. package/dist/MdzNodeView.svelte +118 -112
  5. package/dist/MdzNodeView.svelte.d.ts +6 -2
  6. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  7. package/dist/MdzStream.svelte +3 -3
  8. package/dist/MdzStream.svelte.d.ts.map +1 -1
  9. package/dist/MdzStreamNodeView.svelte +117 -114
  10. package/dist/MdzStreamNodeView.svelte.d.ts +6 -2
  11. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
  12. package/dist/mdz.d.ts +6 -0
  13. package/dist/mdz.d.ts.map +1 -1
  14. package/dist/mdz.js +8 -0
  15. package/dist/mdz_debug_work.d.ts +38 -0
  16. package/dist/mdz_debug_work.d.ts.map +1 -0
  17. package/dist/mdz_debug_work.js +37 -0
  18. package/dist/mdz_helpers.d.ts +61 -0
  19. package/dist/mdz_helpers.d.ts.map +1 -1
  20. package/dist/mdz_helpers.js +72 -0
  21. package/dist/mdz_lexer.d.ts +12 -0
  22. package/dist/mdz_lexer.d.ts.map +1 -1
  23. package/dist/mdz_lexer.js +164 -24
  24. package/dist/mdz_stream_parser_inline.d.ts.map +1 -1
  25. package/dist/mdz_stream_parser_inline.js +19 -17
  26. package/dist/mdz_stream_parser_link.d.ts.map +1 -1
  27. package/dist/mdz_stream_parser_link.js +88 -56
  28. package/dist/mdz_stream_parser_list.d.ts.map +1 -1
  29. package/dist/mdz_stream_parser_list.js +10 -2
  30. package/dist/mdz_stream_parser_state.d.ts +29 -0
  31. package/dist/mdz_stream_parser_state.d.ts.map +1 -1
  32. package/dist/mdz_stream_parser_state.js +52 -3
  33. package/dist/mdz_stream_parser_table.d.ts +3 -2
  34. package/dist/mdz_stream_parser_table.d.ts.map +1 -1
  35. package/dist/mdz_stream_parser_table.js +18 -7
  36. package/dist/mdz_stream_parser_text.d.ts.map +1 -1
  37. package/dist/mdz_stream_parser_text.js +35 -24
  38. package/dist/mdz_to_svelte.d.ts.map +1 -1
  39. package/dist/mdz_to_svelte.js +28 -31
  40. package/dist/mdz_token_parser.d.ts.map +1 -1
  41. package/dist/mdz_token_parser.js +15 -16
  42. package/dist/svelte_preprocess_mdz.js +31 -37
  43. package/package.json +7 -4
  44. package/src/lib/mdz.ts +9 -0
  45. package/src/lib/mdz_debug_work.ts +38 -0
  46. package/src/lib/mdz_helpers.ts +97 -0
  47. package/src/lib/mdz_lexer.ts +168 -28
  48. package/src/lib/mdz_stream_parser_inline.ts +21 -17
  49. package/src/lib/mdz_stream_parser_link.ts +89 -56
  50. package/src/lib/mdz_stream_parser_list.ts +10 -4
  51. package/src/lib/mdz_stream_parser_state.ts +76 -4
  52. package/src/lib/mdz_stream_parser_table.ts +18 -7
  53. package/src/lib/mdz_stream_parser_text.ts +38 -37
  54. package/src/lib/mdz_to_svelte.ts +23 -31
  55. package/src/lib/mdz_token_parser.ts +17 -20
  56. package/src/lib/svelte_preprocess_mdz.ts +34 -35
@@ -8,6 +8,7 @@
8
8
  * @module
9
9
  */
10
10
  import type { MdzNodeTypeContainer, MdzNodeId, MdzOpcode } from './mdz_opcodes.js';
11
+ import type { MdzTableCellParser } from './mdz.js';
11
12
  /**
12
13
  * Tri-state result for `try_*` parser handlers.
13
14
  * - `'consumed'`: input matched and the parser advanced
@@ -206,10 +207,12 @@ export interface MdzStreamParserState {
206
207
  * is `null` for a top-level table (column-0 rows) or the enclosing list item's
207
208
  * marker indent for a table nested as a block child (rows skip the indent and
208
209
  * a dedent to that indent ends the table, returning control to the list).
210
+ * `cell_parser` is the table's shared cell parser, rebound per row.
209
211
  */
210
212
  table: {
211
213
  id: MdzNodeId;
212
214
  item_indent: number | null;
215
+ cell_parser: MdzTableCellParser;
213
216
  } | null;
214
217
  /**
215
218
  * Whether the innermost open ListItem's first inline run is current.
@@ -238,6 +241,32 @@ export interface MdzStreamParserState {
238
241
  search_memo: Map<string, BufferSearchMemo> | null;
239
242
  /** Open-container counts for `find_open`'s O(1) "nothing open" early exit. */
240
243
  open_counts: OpenInlineCounts;
244
+ /**
245
+ * Count of open `Link`/`Element`/`Component` frames in the current run — the
246
+ * streaming analogue of the sync lexer's `#inline_depth`. Kept as an O(1)
247
+ * counter (not a stack walk, which would reintroduce the deep-stack
248
+ * quadratic `open_counts` exists to avoid) so `try_link_open`/`try_tag_open`
249
+ * can cap nesting at `MAX_INLINE_NESTING_DEPTH` — rendering a deeper `[`/`<`
250
+ * literal, matching the sync cap on pure/symmetric nesting. It counts
251
+ * **optimistic** opens, though, so it diverges from the sync `#inline_depth`
252
+ * (which the sync tag path only bumps after a closer precheck) on an
253
+ * asymmetric cap-saturating prefix — 100+ *unclosed* `<a>` before a valid
254
+ * `[x](/u)` saturates this counter and suppresses the trailing link that sync
255
+ * still forms. Adversarial-only, documented not fixed (streaming can't
256
+ * precheck closers): see the flip-at-cap case in `mdz_nesting_cap.test.ts`.
257
+ * Run-scoped for free like `open_counts`: a
258
+ * run close reverts every inline frame above it, decrementing back to zero.
259
+ */
260
+ open_link_tag_depth: number;
261
+ /**
262
+ * Per-name counts of open Element/Component frames, the tag analogue of
263
+ * `open_counts`: `try_close_tag` bails in O(1) when the closing name isn't
264
+ * open, instead of walking a stack that grows one frame per leaked unclosed
265
+ * `<Tag>`. Lazily created on the first tag open — tag-free inputs never
266
+ * allocate the map. Run-scoped like `open_counts`: run closes revert every
267
+ * inline frame above them, decrementing the counts back to zero.
268
+ */
269
+ open_tag_counts: Map<string, number> | null;
241
270
  /** Whether we're inside a heading (newline ends it). */
242
271
  in_heading: boolean;
243
272
  /** Whether we're inside an optimistic inline Code container. */
@@ -1 +1 @@
1
- {"version":3,"file":"mdz_stream_parser_state.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mdz_stream_parser_state.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_state.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@
8
8
  * @module
9
9
  */
10
10
  import { NEWLINE, has_non_whitespace, mdz_heading_id_from_text } from './mdz_helpers.js';
11
+ import { mdz_debug_work } from './mdz_debug_work.js';
11
12
  /** @nodocs */
12
13
  export const create_state = () => ({
13
14
  buffer: '',
@@ -30,6 +31,8 @@ export const create_state = () => ({
30
31
  base_offset: 0,
31
32
  search_memo: null,
32
33
  open_counts: { Bold: 0, Italic: 0, Strikethrough: 0, Link: 0 },
34
+ open_link_tag_depth: 0,
35
+ open_tag_counts: null,
33
36
  in_heading: false,
34
37
  in_code: false,
35
38
  in_paragraph: false,
@@ -69,6 +72,14 @@ export const push_stack_entry = (state, id, node_type, start, delimiter = '', ta
69
72
  else {
70
73
  bump_open_count(state, node_type, 1);
71
74
  }
75
+ if (tag_name !== undefined) {
76
+ const counts = (state.open_tag_counts ??= new Map());
77
+ counts.set(tag_name, (counts.get(tag_name) ?? 0) + 1);
78
+ }
79
+ // Link/Element/Component contribute to the nesting-depth cap (tag_name is
80
+ // set for Element/Component only, so `Link || tag_name` is exactly the three)
81
+ if (node_type === 'Link' || tag_name !== undefined)
82
+ state.open_link_tag_depth++;
72
83
  };
73
84
  /**
74
85
  * Adjust the `open_counts` entry for a counted container type; no-op for
@@ -99,6 +110,19 @@ const bump_open_count = (state, node_type, delta) => {
99
110
  export const pop_stack_entry = (state) => {
100
111
  const entry = state.stack.pop();
101
112
  bump_open_count(state, entry.node_type, -1);
113
+ if (entry.tag_name !== undefined) {
114
+ const counts = state.open_tag_counts;
115
+ // drop zero-count keys so a long stream of many distinct tag names can't
116
+ // grow the map unboundedly — the `!counts.get(name)` bail in `try_close_tag`
117
+ // treats a missing key and a 0 count identically, so this is behavior-neutral
118
+ const remaining = counts.get(entry.tag_name) - 1;
119
+ if (remaining === 0)
120
+ counts.delete(entry.tag_name);
121
+ else
122
+ counts.set(entry.tag_name, remaining);
123
+ }
124
+ if (entry.node_type === 'Link' || entry.tag_name !== undefined)
125
+ state.open_link_tag_depth--;
102
126
  return entry;
103
127
  };
104
128
  /**
@@ -163,7 +187,11 @@ const memo_index_of = (state, memo, needle, from) => {
163
187
  // rescan only the appended tail, overlapping the old boundary so a
164
188
  // straddling needle isn't missed
165
189
  const rescan_from = Math.max(global_from, memo.searched_to - (needle.length - 1));
166
- const result = state.buffer.indexOf(needle, rescan_from - state.base_offset);
190
+ const local_from = rescan_from - state.base_offset;
191
+ const result = state.buffer.indexOf(needle, local_from);
192
+ if (mdz_debug_work.enabled) {
193
+ mdz_debug_work.total += (result === -1 ? state.buffer.length : result) - local_from;
194
+ }
167
195
  memo.from = global_from;
168
196
  memo.result = result === -1 ? -1 : state.base_offset + result;
169
197
  memo.searched_to = global_end;
@@ -172,6 +200,9 @@ const memo_index_of = (state, memo, needle, from) => {
172
200
  // found result already behind `from` — fall through to a fresh scan
173
201
  }
174
202
  const result = state.buffer.indexOf(needle, from);
203
+ if (mdz_debug_work.enabled) {
204
+ mdz_debug_work.total += (result === -1 ? state.buffer.length : result) - from;
205
+ }
175
206
  memo.from = global_from;
176
207
  memo.result = result === -1 ? -1 : state.base_offset + result;
177
208
  memo.searched_to = global_end;
@@ -211,8 +242,11 @@ export const emit = (state, op) => {
211
242
  if (top) {
212
243
  top.has_children = true;
213
244
  // propagate non-whitespace marker to the nearest Paragraph so
214
- // whitespace-only paragraphs can be discarded at close
215
- if (op.type === 'void' || has_non_whitespace(op.content)) {
245
+ // whitespace-only paragraphs can be discarded at close — the O(1)
246
+ // no-paragraph check runs first so codeblock/table-cell emissions
247
+ // (never inside a Paragraph) skip the content scan entirely
248
+ if (op.type === 'void' ||
249
+ (state.paragraph_stack_idx !== -1 && has_non_whitespace(op.content))) {
216
250
  mark_paragraph_non_whitespace(state);
217
251
  }
218
252
  }
@@ -436,6 +470,21 @@ export const revert_failed_close = (state, stack_idx) => {
436
470
  const frames_above = state.stack.length - 1 - stack_idx;
437
471
  state.stack.splice(stack_idx, 1);
438
472
  bump_open_count(state, entry.node_type, -1);
473
+ if (entry.tag_name !== undefined) {
474
+ const counts = state.open_tag_counts;
475
+ // drop zero-count keys so a long stream of many distinct tag names can't
476
+ // grow the map unboundedly — the `!counts.get(name)` bail in `try_close_tag`
477
+ // treats a missing key and a 0 count identically, so this is behavior-neutral
478
+ const remaining = counts.get(entry.tag_name) - 1;
479
+ if (remaining === 0)
480
+ counts.delete(entry.tag_name);
481
+ else
482
+ counts.set(entry.tag_name, remaining);
483
+ }
484
+ // only ever reverts an Italic today, but keep the depth counter honest if a
485
+ // Link/Element/Component is ever failed-closed mid-stack
486
+ if (entry.node_type === 'Link' || entry.tag_name !== undefined)
487
+ state.open_link_tag_depth--;
439
488
  state.opcodes.push({
440
489
  type: 'revert',
441
490
  id: entry.id,
@@ -7,8 +7,9 @@
7
7
  * whole header emits at once. Body rows then stream one per line. Because a row
8
8
  * is always fully buffered before it emits (no within-row streaming), each
9
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.
10
+ * one `MdzLexer`/`MdzTokenParser` shared across the whole table's cells,
11
+ * rebound per row) and replayed as opcodes — guaranteeing sync↔stream parity
12
+ * by construction.
12
13
  *
13
14
  * @module
14
15
  */
@@ -1 +1 @@
1
- {"version":3,"file":"mdz_stream_parser_table.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAWH,OAAO,EAAmC,KAAK,aAAa,EAAC,MAAM,UAAU,CAAC;AAC9E,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,SAAS,EAQd,MAAM,8BAA8B,CAAC;AAEtC;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,oBAAoB,EAAE,QAAQ,OAAO,KAAG,SAqC9E,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,oBAAoB,EAAE,QAAQ,OAAO,KAAG,OAyCjF,CAAC;AA2DF;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,oBAAoB,KAAG,IAczD,CAAC;AAEF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC/B,YAAY,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GACpC,OAAO,oBAAoB,EAC3B,cAAc,MAAM,EACpB,eAAe,MAAM,EACrB,QAAQ,OAAO,KACb,eAAe,GAAG,SAAS,GAAG,IAmBhC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC9B,OAAO,oBAAoB,EAC3B,cAAc,MAAM,EACpB,eAAe,MAAM,EACrB,MAAM,eAAe,KACnB,IAkBF,CAAC"}
1
+ {"version":3,"file":"mdz_stream_parser_table.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAWH,OAAO,EAAmC,KAAK,aAAa,EAAC,MAAM,UAAU,CAAC;AAC9E,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,SAAS,EAQd,MAAM,8BAA8B,CAAC;AAEtC;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,oBAAoB,EAAE,QAAQ,OAAO,KAAG,SAyC9E,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,oBAAoB,EAAE,QAAQ,OAAO,KAAG,OAyCjF,CAAC;AA2DF;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,oBAAoB,KAAG,IAczD,CAAC;AAEF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC/B,YAAY,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GACpC,OAAO,oBAAoB,EAC3B,cAAc,MAAM,EACpB,eAAe,MAAM,EACrB,QAAQ,OAAO,KACb,eAAe,GAAG,SAAS,GAAG,IAmBhC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC9B,OAAO,oBAAoB,EAC3B,cAAc,MAAM,EACpB,eAAe,MAAM,EACrB,MAAM,eAAe,KACnB,IAsBF,CAAC"}
@@ -7,8 +7,9 @@
7
7
  * whole header emits at once. Body rows then stream one per line. Because a row
8
8
  * is always fully buffered before it emits (no within-row streaming), each
9
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.
10
+ * one `MdzLexer`/`MdzTokenParser` shared across the whole table's cells,
11
+ * rebound per row) and replayed as opcodes — guaranteeing sync↔stream parity
12
+ * by construction.
12
13
  *
13
14
  * @module
14
15
  */
@@ -52,7 +53,11 @@ export const try_table_start = (state, forced) => {
52
53
  const table_start = offset(state, start);
53
54
  emit(state, { type: 'open', id: table_id, node_type: 'Table', start: table_start, align });
54
55
  push_stack_entry(state, table_id, 'Table', table_start);
55
- state.table = { id: table_id, item_indent: null };
56
+ state.table = {
57
+ id: table_id,
58
+ item_indent: null,
59
+ cell_parser: new MdzTableCellParser(state.buffer),
60
+ };
56
61
  emit_table_row(state, header_cells, true, start, header_end);
57
62
  // skip past the delimiter row to the first potential body line
58
63
  state.pos = delim_end === state.buffer.length ? delim_end : delim_end + 1;
@@ -236,7 +241,11 @@ export const open_table_in_item = (state, header_first, marker_indent, head) =>
236
241
  align: head.align,
237
242
  });
238
243
  push_stack_entry(state, table_id, 'Table', table_start);
239
- state.table = { id: table_id, item_indent: marker_indent };
244
+ state.table = {
245
+ id: table_id,
246
+ item_indent: marker_indent,
247
+ cell_parser: new MdzTableCellParser(state.buffer),
248
+ };
240
249
  emit_table_row(state, head.header_cells, true, header_first, head.header_end);
241
250
  // leave the cursor on the delimiter's newline (or EOF) — the in-item body-row
242
251
  // handler reads from the line after it and hands back on this same convention
@@ -250,9 +259,11 @@ const emit_table_row = (state, cells, header, row_start, row_end) => {
250
259
  const row_off = offset(state, row_start);
251
260
  emit(state, { type: 'open', id: row_id, node_type: 'TableRow', start: row_off, header });
252
261
  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);
262
+ // one lexer/parser shared across the whole table rather than allocated per
263
+ // row (or per cell) — rebound per row because the buffer string changes
264
+ // across feeds; the lexer's search memo survives when it doesn't
265
+ const cell_parser = state.table.cell_parser;
266
+ cell_parser.rebind(state.buffer);
256
267
  for (const cell of cells) {
257
268
  emit_table_cell(state, cell_parser, cell.start, cell.end);
258
269
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mdz_stream_parser_text.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_text.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH,OAAO,EACN,KAAK,oBAAoB,EAIzB,MAAM,8BAA8B,CAAC;AAEtC;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,oBAAoB,EAAE,WAAW,MAAM,KAAG,IAM1F,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,oBAAoB,KAAG,IAsC9D,CAAC"}
1
+ {"version":3,"file":"mdz_stream_parser_text.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_text.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,OAAO,EACN,KAAK,oBAAoB,EAIzB,MAAM,8BAA8B,CAAC;AAGtC;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,oBAAoB,EAAE,WAAW,MAAM,KAAG,IAM1F,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,oBAAoB,KAAG,IA4C9D,CAAC"}
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * @module
5
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';
6
+ import { HTTPS_PREFIX_LENGTH, NEWLINE, SPACE, TAB, TEXT_CLASS_NEWLINE, TEXT_CLASS_PATH, TEXT_CLASS_STOP, TEXT_CLASS_URL, TEXT_SCAN_CLASS, is_word_char, match_url_prefix_case_insensitive, } from './mdz_helpers.js';
7
7
  import { accumulate_text, ensure_paragraph, offset, } from './mdz_stream_parser_state.js';
8
+ import { mdz_debug_work } from './mdz_debug_work.js';
8
9
  /**
9
10
  * Consume a delimiter at the current position as literal text — the shared
10
11
  * tail of every "this delimiter doesn't open/close anything here" decision
@@ -34,33 +35,43 @@ export const consume_text_run = (state) => {
34
35
  state.pos++;
35
36
  while (state.pos < state.buffer.length) {
36
37
  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))) {
38
+ // one table load classifies the char; zero (the common case) continues
39
+ // the run without any further checks. `TEXT_SCAN_CLASS` is shared with
40
+ // the sync lexer's `#tokenize_text` (`mdz_lexer.ts`); the dispatch bodies
41
+ // intentionally differ — that one handles `\|` escapes and list-run
42
+ // strip, this one URL speculation across chunk boundaries.
43
+ const char_class = c < 128 ? TEXT_SCAN_CLASS[c] : 0;
44
+ if (char_class === 0) {
45
+ state.pos++;
46
+ continue;
47
+ }
48
+ if ((char_class & (TEXT_CLASS_STOP | TEXT_CLASS_NEWLINE)) !== 0)
60
49
  break;
50
+ if ((char_class & TEXT_CLASS_URL) !== 0) {
51
+ // URL: only break when actually at a URL start. Most h/H in prose are
52
+ // not URLs — continuing the scan avoids the full dispatch cycle
53
+ // through process_loop → process_inline. At a word boundary, also
54
+ // break when there isn't enough buffer left to confirm `https://` /
55
+ // `http://` — process_inline's speculator then carries the prefix
56
+ // match across chunk boundaries. Scheme matching is case-insensitive
57
+ // (RFC 3986).
58
+ if (match_url_prefix_case_insensitive(state.buffer, state.pos) > 0 ||
59
+ (state.buffer.length - state.pos < HTTPS_PREFIX_LENGTH &&
60
+ !is_word_char(state.buffer.charCodeAt(state.pos - 1)))) {
61
+ break;
62
+ }
63
+ }
64
+ else if ((char_class & TEXT_CLASS_PATH) !== 0) {
65
+ // path (`/` or `.`): only break at a plausible path start
66
+ const prev = state.buffer.charCodeAt(state.pos - 1);
67
+ if (prev === SPACE || prev === NEWLINE || prev === TAB)
68
+ break;
61
69
  }
70
+ // backslash (or a non-starting path char) — ordinary text
62
71
  state.pos++;
63
72
  }
73
+ if (mdz_debug_work.enabled)
74
+ mdz_debug_work.total += state.pos - start;
64
75
  accumulate_text(state, state.buffer.slice(start, state.pos), offset(state, start));
65
76
  state.prev_char = state.buffer.charCodeAt(state.pos - 1);
66
77
  state.column += state.pos - start;
@@ -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,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"}
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,iBAsJF,CAAC"}
@@ -10,7 +10,7 @@
10
10
  import { UnreachableError } from '@fuzdev/fuz_util/error.ts';
11
11
  import { escape_svelte_text } from '@fuzdev/fuz_util/svelte_preprocess_helpers.ts';
12
12
  import { escape_js_string } from '@fuzdev/fuz_util/string.ts';
13
- import { mdz_resolve_relative_path, mdz_is_void_element } from './mdz_helpers.js';
13
+ import { mdz_is_void_element, mdz_classify_link } 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$/, '');
16
16
  /**
@@ -58,24 +58,22 @@ export const mdz_to_svelte = (nodes, options) => {
58
58
  return `<s>${render_nodes(node.children)}</s>`;
59
59
  case 'Link': {
60
60
  const children_markup = render_nodes(node.children);
61
- if (node.link_type === 'internal') {
62
- if (node.reference.startsWith('.') && base) {
63
- const resolved = mdz_resolve_relative_path(node.reference, base);
64
- imports.set('resolve', { path: '$app/paths', kind: 'named' });
65
- return `<a href={resolve('${escape_js_string(resolved)}')}>${children_markup}</a>`;
66
- }
67
- if (node.reference.startsWith('#') ||
68
- node.reference.startsWith('?') ||
69
- node.reference.startsWith('.') ||
70
- // Bare references (e.g. `foo`) aren't absolute — resolve() throws on them
71
- !node.reference.startsWith('/')) {
72
- return `<a href={'${escape_js_string(node.reference)}'}>${children_markup}</a>`;
73
- }
61
+ // safety gate + resolve-vs-raw classification shared with the runtime
62
+ // views via `mdz_classify_link`, so the three renderers can't drift
63
+ // (an unsafe `javascript:`/`data:` ref renders as plain children, no
64
+ // `<a>` — defense in depth, since `mdz_to_svelte` is a public entry
65
+ // callable with hand-built trees that never went through `mdz_parse`)
66
+ const link = mdz_classify_link(node.reference, node.link_type, base);
67
+ if (link.kind === 'unsafe')
68
+ return children_markup;
69
+ if (link.kind === 'resolve') {
74
70
  imports.set('resolve', { path: '$app/paths', kind: 'named' });
75
- return `<a href={resolve('${escape_js_string(node.reference)}')}>${children_markup}</a>`;
71
+ return `<a href={resolve('${escape_js_string(link.href)}')}>${children_markup}</a>`;
72
+ }
73
+ if (link.kind === 'external') {
74
+ return `<a href={'${escape_js_string(link.href)}'} target="_blank" rel="noopener">${children_markup}</a>`;
76
75
  }
77
- // External link — matches MdzNodeView: target="_blank" rel="noopener"
78
- return `<a href={'${escape_js_string(node.reference)}'} target="_blank" rel="noopener">${children_markup}</a>`;
76
+ return `<a href={'${escape_js_string(link.href)}'}>${children_markup}</a>`;
79
77
  }
80
78
  case 'Paragraph':
81
79
  return `<p>${render_nodes(node.children)}</p>`;
@@ -103,21 +101,20 @@ export const mdz_to_svelte = (nodes, options) => {
103
101
  }
104
102
  return `<tr>${cells}</tr>`;
105
103
  };
104
+ // by grammar the header row is always first (and the only one) —
105
+ // read it by index and skip it in the body, mirroring the node
106
+ // views (a `.filter()` pair re-partitions every row; see
107
+ // `MdzNodeView.svelte`'s "Table rendering de-quadratified" note)
106
108
  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
- }
109
+ const header_row = node.children[0]?.header ? node.children[0] : undefined;
110
+ if (header_row)
111
+ out += '<thead>' + render_row(header_row, 'th') + '</thead>';
112
+ let body = '';
113
+ for (const row of node.children)
114
+ if (!row.header)
115
+ body += render_row(row, 'td');
116
+ if (body)
117
+ out += '<tbody>' + body + '</tbody>';
121
118
  return out + '</table>';
122
119
  }
123
120
  case 'TableRow':
@@ -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,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"}
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;AAiBxB;;;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;CA+iBlC"}
@@ -8,6 +8,19 @@
8
8
  */
9
9
  import { has_non_whitespace, mdz_extract_single_tag, mdz_heading_id, mdz_merge_adjacent_text, mdz_push_merging_text, } from './mdz_helpers.js';
10
10
  import {} from './mdz_lexer.js';
11
+ /**
12
+ * Whether a token type ends the current inline context — the block-boundary
13
+ * set shared by the delimiter-pair and link children loops (an unclosed
14
+ * inline never swallows block structure).
15
+ */
16
+ const token_is_block_boundary = (type) => type === 'paragraph_break' ||
17
+ type === 'heading_start' ||
18
+ type === 'hr' ||
19
+ type === 'codeblock' ||
20
+ type === 'list_open' ||
21
+ type === 'list_item_close' ||
22
+ type === 'blockquote_open' ||
23
+ type === 'table_open';
11
24
  /**
12
25
  * Builds an `MdzNode[]` tree from a lexed `MdzToken[]` stream.
13
26
  * Used by `mdz_parse`, which should be preferred for simple usage.
@@ -398,14 +411,7 @@ export class MdzTokenParser {
398
411
  this.#index++;
399
412
  return { type: node_type, children, start, end: t.end };
400
413
  }
401
- if (t.type === 'paragraph_break' ||
402
- t.type === 'heading_start' ||
403
- t.type === 'hr' ||
404
- t.type === 'codeblock' ||
405
- t.type === 'list_open' ||
406
- t.type === 'list_item_close' ||
407
- t.type === 'blockquote_open' ||
408
- t.type === 'table_open') {
414
+ if (token_is_block_boundary(t.type)) {
409
415
  break;
410
416
  }
411
417
  const node = this.#parse_inline();
@@ -440,14 +446,7 @@ export class MdzTokenParser {
440
446
  // No link_ref - treat as text
441
447
  return { type: 'Text', content: '[', start, end: start + 1 };
442
448
  }
443
- if (t.type === 'paragraph_break' ||
444
- t.type === 'heading_start' ||
445
- t.type === 'hr' ||
446
- t.type === 'codeblock' ||
447
- t.type === 'list_open' ||
448
- t.type === 'list_item_close' ||
449
- t.type === 'blockquote_open' ||
450
- t.type === 'table_open') {
449
+ if (token_is_block_boundary(t.type)) {
451
450
  break;
452
451
  }
453
452
  const node = this.#parse_inline();
@@ -138,6 +138,29 @@ const collect_consumed_bindings = (value, bindings) => {
138
138
  collect_from_expr(value.expression);
139
139
  return consumed;
140
140
  };
141
+ /**
142
+ * Parse a static mdz string and render it to Svelte markup. Returns `null` to
143
+ * leave the usage for the runtime — either the content raised a parse error
144
+ * (reported via `on_error`) or it contains unconfigured tags (which the
145
+ * precompiler emits nothing for; the runtime renders the fallback chrome).
146
+ */
147
+ const render_static_mdz = (content, base, context) => {
148
+ let result;
149
+ try {
150
+ result = mdz_to_svelte(mdz_parse(content), {
151
+ components: context.components,
152
+ elements: context.elements,
153
+ base,
154
+ code_component_import: context.code_component_import,
155
+ codeblock_component_import: context.codeblock_component_import,
156
+ });
157
+ }
158
+ catch (error) {
159
+ handle_preprocess_error(error, '[fuz-mdz]', context.filename, context.on_error);
160
+ return null;
161
+ }
162
+ return result.has_unconfigured_tags ? null : result;
163
+ };
141
164
  /**
142
165
  * Walks the AST to find `Mdz` component usages with static `content` props
143
166
  * and generates transformations to replace them with `MdzPrecompiled` children.
@@ -181,25 +204,9 @@ const find_mdz_usages = (ast, mdz_names, context) => {
181
204
  // Extract static string value
182
205
  const content_value = extract_static_string(content_attr.value, context.bindings);
183
206
  if (content_value !== null) {
184
- // Parse mdz content and render to Svelte markup
185
- let result;
186
- try {
187
- const nodes = mdz_parse(content_value);
188
- result = mdz_to_svelte(nodes, {
189
- components: context.components,
190
- elements: context.elements,
191
- base,
192
- code_component_import: context.code_component_import,
193
- codeblock_component_import: context.codeblock_component_import,
194
- });
195
- }
196
- catch (error) {
197
- handle_preprocess_error(error, '[fuz-mdz]', context.filename, context.on_error);
198
- return;
199
- }
200
- // If content has unconfigured tags, skip this usage (fall back to runtime)
201
- if (result.has_unconfigured_tags)
202
- return;
207
+ const result = render_static_mdz(content_value, base, context);
208
+ if (result === null)
209
+ return; // parse error or unconfigured tags — leave for runtime
203
210
  const consumed = collect_consumed_bindings(content_attr.value, context.bindings);
204
211
  const replacement = build_replacement(node, exclude_attrs, result.markup, context.source);
205
212
  transformed_usages.set(node.name, (transformed_usages.get(node.name) ?? 0) + 1);
@@ -219,24 +226,11 @@ const find_mdz_usages = (ast, mdz_names, context) => {
219
226
  return;
220
227
  // Parse and render each branch
221
228
  const branch_results = [];
222
- try {
223
- for (const branch of chain) {
224
- const nodes = mdz_parse(branch.value);
225
- const result = mdz_to_svelte(nodes, {
226
- components: context.components,
227
- elements: context.elements,
228
- base,
229
- code_component_import: context.code_component_import,
230
- codeblock_component_import: context.codeblock_component_import,
231
- });
232
- if (result.has_unconfigured_tags)
233
- return;
234
- branch_results.push({ markup: result.markup, imports: result.imports });
235
- }
236
- }
237
- catch (error) {
238
- handle_preprocess_error(error, '[fuz-mdz]', context.filename, context.on_error);
239
- return;
229
+ for (const branch of chain) {
230
+ const result = render_static_mdz(branch.value, base, context);
231
+ if (result === null)
232
+ return; // parse error or unconfigured tags — leave for runtime
233
+ branch_results.push({ markup: result.markup, imports: result.imports });
240
234
  }
241
235
  // Build {#if}/{:else if}/{:else} markup
242
236
  let children_markup = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/mdz",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "a strict markdown dialect built for streaming, Svelte authoring, docs websites, and untrusted content",
5
5
  "tagline": "markdown for fuz",
6
6
  "glyph": "📝",
@@ -30,7 +30,10 @@
30
30
  "deploy": "gro deploy",
31
31
  "benchmark": "gro run src/benchmarks/mdz.benchmark.ts",
32
32
  "benchmark:save": "gro run src/benchmarks/mdz.benchmark.ts --save",
33
- "benchmark:clean": "rm -f src/benchmarks/baseline.json"
33
+ "benchmark:clean": "rm -f src/benchmarks/baseline.json",
34
+ "benchmark:render": "BENCHMARK_RENDER=1 gro test render.benchmark",
35
+ "benchmark:render:save": "BENCHMARK_RENDER=save gro test render.benchmark",
36
+ "benchmark:render:clean": "rm -f src/benchmarks/render/baseline.json"
34
37
  },
35
38
  "type": "module",
36
39
  "engines": {
@@ -67,7 +70,7 @@
67
70
  "@sveltejs/adapter-static": "^3.0.10",
68
71
  "@sveltejs/kit": "2.64.0",
69
72
  "@sveltejs/package": "^2.5.7",
70
- "@sveltejs/vite-plugin-svelte": "^7.1.2",
73
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
71
74
  "@types/estree": "^1.0.8",
72
75
  "@types/node": "^24.12.4",
73
76
  "@webref/css": "^8.2.0",
@@ -83,7 +86,7 @@
83
86
  "tslib": "^2.8.1",
84
87
  "typescript": "^5.9.3",
85
88
  "typescript-eslint": "^8.48.1",
86
- "vite": "^8.0.16",
89
+ "vite": "^7.3.6",
87
90
  "vitest": "^4.0.15"
88
91
  },
89
92
  "prettier": {