@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.
- package/dist/Mdz.svelte +15 -7
- package/dist/Mdz.svelte.d.ts +9 -2
- package/dist/Mdz.svelte.d.ts.map +1 -1
- package/dist/MdzNodeView.svelte +118 -112
- package/dist/MdzNodeView.svelte.d.ts +6 -2
- package/dist/MdzNodeView.svelte.d.ts.map +1 -1
- package/dist/MdzStream.svelte +3 -3
- package/dist/MdzStream.svelte.d.ts.map +1 -1
- package/dist/MdzStreamNodeView.svelte +117 -114
- package/dist/MdzStreamNodeView.svelte.d.ts +6 -2
- package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
- package/dist/mdz.d.ts +6 -0
- package/dist/mdz.d.ts.map +1 -1
- package/dist/mdz.js +8 -0
- package/dist/mdz_debug_work.d.ts +38 -0
- package/dist/mdz_debug_work.d.ts.map +1 -0
- package/dist/mdz_debug_work.js +37 -0
- package/dist/mdz_helpers.d.ts +61 -0
- package/dist/mdz_helpers.d.ts.map +1 -1
- package/dist/mdz_helpers.js +72 -0
- package/dist/mdz_lexer.d.ts +12 -0
- package/dist/mdz_lexer.d.ts.map +1 -1
- package/dist/mdz_lexer.js +164 -24
- package/dist/mdz_stream_parser_inline.d.ts.map +1 -1
- package/dist/mdz_stream_parser_inline.js +19 -17
- package/dist/mdz_stream_parser_link.d.ts.map +1 -1
- package/dist/mdz_stream_parser_link.js +88 -56
- package/dist/mdz_stream_parser_list.d.ts.map +1 -1
- package/dist/mdz_stream_parser_list.js +10 -2
- package/dist/mdz_stream_parser_state.d.ts +29 -0
- package/dist/mdz_stream_parser_state.d.ts.map +1 -1
- package/dist/mdz_stream_parser_state.js +52 -3
- package/dist/mdz_stream_parser_table.d.ts +3 -2
- package/dist/mdz_stream_parser_table.d.ts.map +1 -1
- package/dist/mdz_stream_parser_table.js +18 -7
- package/dist/mdz_stream_parser_text.d.ts.map +1 -1
- package/dist/mdz_stream_parser_text.js +35 -24
- package/dist/mdz_to_svelte.d.ts.map +1 -1
- package/dist/mdz_to_svelte.js +28 -31
- package/dist/mdz_token_parser.d.ts.map +1 -1
- package/dist/mdz_token_parser.js +15 -16
- package/dist/svelte_preprocess_mdz.js +31 -37
- package/package.json +7 -4
- package/src/lib/mdz.ts +9 -0
- package/src/lib/mdz_debug_work.ts +38 -0
- package/src/lib/mdz_helpers.ts +97 -0
- package/src/lib/mdz_lexer.ts +168 -28
- package/src/lib/mdz_stream_parser_inline.ts +21 -17
- package/src/lib/mdz_stream_parser_link.ts +89 -56
- package/src/lib/mdz_stream_parser_list.ts +10 -4
- package/src/lib/mdz_stream_parser_state.ts +76 -4
- package/src/lib/mdz_stream_parser_table.ts +18 -7
- package/src/lib/mdz_stream_parser_text.ts +38 -37
- package/src/lib/mdz_to_svelte.ts +23 -31
- package/src/lib/mdz_token_parser.ts +17 -20
- 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":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,oBAAoB,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"mdz_stream_parser_state.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,oBAAoB,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,kBAAkB,CAAC;AACjF,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,UAAU,CAAC;AAIjD;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;AAE/D,cAAc;AACd,MAAM,WAAW,UAAU;IAC1B,EAAE,EAAE,SAAS,CAAC;IACd,SAAS,EAAE,oBAAoB,CAAC;IAChC;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,wEAAwE;IACxE,YAAY,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,0BAA0B,EAAE,OAAO,CAAC;IACpC,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAChC,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,cAAc;AACd,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,SAAS,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,kBAAkB;IAClC,sCAAsC;IACtC,EAAE,EAAE,SAAS,CAAC;IACd,gEAAgE;IAChE,KAAK,EAAE,oBAAoB,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,EAAE,KAAK,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IAC7D,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,eAAe,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxC,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,SAAS,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC;;;;;OAKG;IACH,WAAW,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IACjC;;;OAGG;IACH,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACtC;;;;;;;;OAQG;IACH,KAAK,EAAE;QAAC,EAAE,EAAE,SAAS,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,WAAW,EAAE,kBAAkB,CAAA;KAAC,GAAG,IAAI,CAAC;IAC3F;;;;;;OAMG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;;;;;OAOG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,IAAI,CAAC;IAClD,8EAA8E;IAC9E,WAAW,EAAE,gBAAgB,CAAC;IAC9B;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC5C,wDAAwD;IACxD,UAAU,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,OAAO,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClC;;;;;;;OAOG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;;;;OAMG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,YAAY,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,uEAAuE;IACvE,4BAA4B,EAAE,OAAO,CAAC;IACtC;;;;;OAKG;IACH,+BAA+B,EAAE,OAAO,CAAC;CACzC;AAED,cAAc;AACd,eAAO,MAAM,YAAY,QAAO,oBAmC9B,CAAC;AAEH,cAAc;AACd,eAAO,MAAM,QAAQ,GAAI,OAAO,oBAAoB,KAAG,SAA4B,CAAC;AAEpF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAC5B,OAAO,oBAAoB,EAC3B,IAAI,SAAS,EACb,WAAW,oBAAoB,EAC/B,OAAO,MAAM,EACb,YAAW,MAAW,EACtB,WAAW,MAAM,KACf,IAsBF,CAAC;AAqBF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,oBAAoB,KAAG,UAc7D,CAAC;AAeF;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,oBAAoB,EAAE,MAAK,MAAkB,KAAG,MACtD,CAAC;AAuEzB;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC3B,OAAO,oBAAoB,EAC3B,QAAQ,MAAM,EACd,MAAM,MAAM,KACV,MASF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,OAAO,oBAAoB,EAAE,IAAI,SAAS,KAAG,IAkEjE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAC3B,OAAO,oBAAoB,EAC3B,MAAM,MAAM,EACZ,cAAc,MAAM,KAClB,IAMF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,oBAAoB,EAAE,KAAK,MAAM,KAAG,IAEjF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,oBAAoB,KAAG,IAwBxD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,oBAAoB,KAAG,IAO9D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,WAAW,oBAAoB,KAAG,OACe,CAAC;AAElF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,oBAAoB,EAAE,MAAM,MAAM,gBAAgB,KAAG,MAiBrF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,oBAAoB,EAAE,YAAY,MAAM,KAAG,IAY9E,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,oBAAoB,EAAE,WAAW,MAAM,KAAG,IAoCpF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,oBAAoB,KAAG,IAgBnE,CAAC;AAEF,cAAc;AACd,eAAO,MAAM,eAAe,GAAI,OAAO,oBAAoB,KAAG,IAwB7D,CAAC;AAEF,cAAc;AACd,eAAO,MAAM,aAAa,GAAI,OAAO,oBAAoB,KAAG,IAwB3D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,oBAAoB,KAAG,IASpE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,oBAAoB,KAAG,IAWhE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,oBAAoB,KAAG,IAO5D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,oBAAoB,KAAG,IAQ7D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,oBAAoB,KAAG,IAK9D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,oBAAoB,KAAG,IAMnE,CAAC;AAEF,cAAc;AACd,eAAO,MAAM,sBAAsB,GAAI,OAAO,oBAAoB,KAAG,IAepE,CAAC"}
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
11
|
-
* as opcodes — guaranteeing sync↔stream parity
|
|
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
|
|
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
|
|
11
|
-
* as opcodes — guaranteeing sync↔stream parity
|
|
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 = {
|
|
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 = {
|
|
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
|
|
254
|
-
//
|
|
255
|
-
|
|
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;
|
|
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 {
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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,
|
|
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"}
|
package/dist/mdz_to_svelte.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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;
|
|
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"}
|
package/dist/mdz_token_parser.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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.
|
|
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": "^
|
|
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": "^
|
|
89
|
+
"vite": "^7.3.6",
|
|
87
90
|
"vitest": "^4.0.15"
|
|
88
91
|
},
|
|
89
92
|
"prettier": {
|