@fuzdev/mdz 0.1.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/README.md +4 -0
- 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 +130 -95
- 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 +129 -95
- package/dist/MdzStreamNodeView.svelte.d.ts +6 -2
- package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
- package/dist/context_helpers.d.ts +1 -1
- package/dist/mdz.d.ts +59 -2
- package/dist/mdz.d.ts.map +1 -1
- package/dist/mdz.js +37 -1
- 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 +96 -1
- package/dist/mdz_helpers.d.ts.map +1 -1
- package/dist/mdz_helpers.js +192 -0
- package/dist/mdz_lexer.d.ts +47 -1
- package/dist/mdz_lexer.d.ts.map +1 -1
- package/dist/mdz_lexer.js +364 -15
- package/dist/mdz_opcodes.d.ts +6 -1
- package/dist/mdz_opcodes.d.ts.map +1 -1
- package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -1
- package/dist/mdz_opcodes_to_nodes.js +26 -0
- package/dist/mdz_stream_parser.d.ts.map +1 -1
- package/dist/mdz_stream_parser.js +20 -1
- 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 +32 -3
- package/dist/mdz_stream_parser_state.d.ts +41 -0
- package/dist/mdz_stream_parser_state.d.ts.map +1 -1
- package/dist/mdz_stream_parser_state.js +53 -3
- package/dist/mdz_stream_parser_table.d.ts +73 -0
- package/dist/mdz_stream_parser_table.d.ts.map +1 -0
- package/dist/mdz_stream_parser_table.js +362 -0
- package/dist/mdz_stream_parser_text.d.ts.map +1 -1
- package/dist/mdz_stream_parser_text.js +35 -24
- package/dist/mdz_stream_state.svelte.d.ts +5 -0
- package/dist/mdz_stream_state.svelte.d.ts.map +1 -1
- package/dist/mdz_stream_state.svelte.js +8 -0
- package/dist/mdz_to_svelte.d.ts.map +1 -1
- package/dist/mdz_to_svelte.js +51 -20
- package/dist/mdz_token_parser.d.ts +16 -0
- package/dist/mdz_token_parser.d.ts.map +1 -1
- package/dist/mdz_token_parser.js +112 -14
- package/dist/svelte_preprocess_mdz.js +33 -39
- package/package.json +11 -8
- package/src/lib/context_helpers.ts +1 -1
- package/src/lib/mdz.ts +79 -3
- package/src/lib/mdz_contexts.ts +1 -1
- package/src/lib/mdz_debug_work.ts +38 -0
- package/src/lib/mdz_helpers.ts +222 -1
- package/src/lib/mdz_lexer.ts +420 -21
- package/src/lib/mdz_opcodes.ts +10 -1
- package/src/lib/mdz_opcodes_to_nodes.ts +37 -3
- package/src/lib/mdz_stream_parser.ts +26 -9
- package/src/lib/mdz_stream_parser_block.ts +2 -2
- package/src/lib/mdz_stream_parser_inline.ts +25 -21
- package/src/lib/mdz_stream_parser_link.ts +92 -59
- package/src/lib/mdz_stream_parser_list.ts +34 -6
- package/src/lib/mdz_stream_parser_state.ts +87 -5
- package/src/lib/mdz_stream_parser_table.ts +420 -0
- package/src/lib/mdz_stream_parser_text.ts +40 -39
- package/src/lib/mdz_stream_parser_url.ts +2 -2
- package/src/lib/mdz_stream_state.svelte.ts +8 -1
- package/src/lib/mdz_to_svelte.ts +49 -23
- package/src/lib/mdz_token_parser.ts +135 -21
- package/src/lib/svelte_preprocess_mdz.ts +37 -38
- package/src/lib/tsdoc_mdz.ts +1 -1
package/src/lib/mdz_helpers.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type {MdzNode, MdzNodeText, MdzNodeComponent, MdzNodeElement} from './mdz.
|
|
10
|
+
import type {MdzNode, MdzNodeText, MdzNodeComponent, MdzNodeElement, MdzTableAlign} from './mdz.ts';
|
|
11
11
|
|
|
12
12
|
// Character codes for performance
|
|
13
13
|
/** @nodocs */
|
|
@@ -47,6 +47,10 @@ export const RIGHT_PAREN = 41; // )
|
|
|
47
47
|
/** @nodocs */
|
|
48
48
|
export const COLON = 58; // :
|
|
49
49
|
/** @nodocs */
|
|
50
|
+
export const PIPE = 124; // |
|
|
51
|
+
/** @nodocs */
|
|
52
|
+
export const BACKSLASH = 92; // \
|
|
53
|
+
/** @nodocs */
|
|
50
54
|
export const PERIOD = 46; // .
|
|
51
55
|
/** @nodocs */
|
|
52
56
|
export const COMMA = 44; // ,
|
|
@@ -95,6 +99,23 @@ export const MIN_CODEBLOCK_BACKTICKS = 3; // Code blocks require minimum 3 backt
|
|
|
95
99
|
export const MAX_LIST_NUMBER_DIGITS = 9; // Ordered list markers cap at 9 digits (CommonMark's cap)
|
|
96
100
|
/** @nodocs */
|
|
97
101
|
export const MAX_HEADING_LEVEL = 6; // Headings support levels 1-6
|
|
102
|
+
/**
|
|
103
|
+
* Cap on inline link/tag nesting depth. Reached at this many open
|
|
104
|
+
* `Link`/`Element`/`Component` containers, a further `[` or `<tag>` opener
|
|
105
|
+
* renders as literal text in both parsers rather than opening — a strict
|
|
106
|
+
* untrusted-content dialect's bound on pathological nesting (CommonMark
|
|
107
|
+
* permits such a limit). It bounds both the sync lexer's recursion depth (no
|
|
108
|
+
* stack overflow) and, with the failure memo, its revert re-scan work (linear
|
|
109
|
+
* instead of quadratic) on adversarial nested `[`/`<`. Deliberately far above
|
|
110
|
+
* any real content (inline nesting rarely exceeds a handful); only pathological
|
|
111
|
+
* input reaches it. Mirrored in the streaming parser so the two agree on where
|
|
112
|
+
* the cap bites. Delimiters (`**`/`_`/`~~`/`` ` ``) are uncounted: first-closer-wins
|
|
113
|
+
* keeps them from nesting deeply on their own, and mixed delimiter/link chains
|
|
114
|
+
* only deep-nest through the links, which this bounds.
|
|
115
|
+
*
|
|
116
|
+
* @nodocs
|
|
117
|
+
*/
|
|
118
|
+
export const MAX_INLINE_NESTING_DEPTH = 100;
|
|
98
119
|
/** @nodocs */
|
|
99
120
|
export const HTTPS_PREFIX_LENGTH = 8; // Length of "https://"
|
|
100
121
|
/** @nodocs */
|
|
@@ -287,6 +308,126 @@ export const mdz_remap_segments = (
|
|
|
287
308
|
return seg.source_start + (p - seg.inner_start);
|
|
288
309
|
};
|
|
289
310
|
|
|
311
|
+
// -- Table helpers --
|
|
312
|
+
|
|
313
|
+
/** Trim line-whitespace from both ends of a `[start, end)` span. */
|
|
314
|
+
const trim_table_cell = (
|
|
315
|
+
text: string,
|
|
316
|
+
start: number,
|
|
317
|
+
end: number,
|
|
318
|
+
): {start: number; end: number} => {
|
|
319
|
+
let s = start;
|
|
320
|
+
let e = end;
|
|
321
|
+
while (s < e && is_line_whitespace(text.charCodeAt(s))) s++;
|
|
322
|
+
while (e > s && is_line_whitespace(text.charCodeAt(e - 1))) e--;
|
|
323
|
+
return {start: s, end: e};
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Split a table row line `[line_start, line_end)` into its cell-content spans.
|
|
328
|
+
*
|
|
329
|
+
* A valid row starts with `|` and ends — after optional trailing whitespace —
|
|
330
|
+
* with `|`, yielding at least one cell. Returns each cell's trimmed
|
|
331
|
+
* `[start, end)` source span, or `null` when the line is not a valid pipe row
|
|
332
|
+
* (no leading `|`, no trailing `|`, or zero cells).
|
|
333
|
+
*
|
|
334
|
+
* Pipe-finding honors two protections so the cell content can hold a literal
|
|
335
|
+
* `|`: an inline-code span (a `` ` `` to its matching `` ` `` on the same line —
|
|
336
|
+
* an unclosed `` ` `` is literal and protects nothing) shields the pipes inside
|
|
337
|
+
* it, and a `\|` escape is consumed as a literal pipe. The escape is
|
|
338
|
+
* table-cell-scoped — the caller's inline tokenizer performs the matching
|
|
339
|
+
* `\|` → `|` substitution; this scanner only declines to split there.
|
|
340
|
+
*
|
|
341
|
+
* @nodocs
|
|
342
|
+
*/
|
|
343
|
+
export const mdz_split_table_row = (
|
|
344
|
+
text: string,
|
|
345
|
+
line_start: number,
|
|
346
|
+
line_end: number,
|
|
347
|
+
): Array<{start: number; end: number}> | null => {
|
|
348
|
+
if (line_start >= line_end || text.charCodeAt(line_start) !== PIPE) return null;
|
|
349
|
+
const cells: Array<{start: number; end: number}> = [];
|
|
350
|
+
let i = line_start + 1; // past the leading pipe
|
|
351
|
+
let cell_start = i;
|
|
352
|
+
while (i < line_end) {
|
|
353
|
+
const char_code = text.charCodeAt(i);
|
|
354
|
+
if (char_code === BACKTICK) {
|
|
355
|
+
// inline-code span: skip to the matching backtick; unclosed → literal
|
|
356
|
+
let close = -1;
|
|
357
|
+
for (let k = i + 1; k < line_end; k++) {
|
|
358
|
+
if (text.charCodeAt(k) === BACKTICK) {
|
|
359
|
+
close = k;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (close === -1) {
|
|
364
|
+
i++;
|
|
365
|
+
} else {
|
|
366
|
+
i = close + 1;
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (char_code === BACKSLASH) {
|
|
371
|
+
// `\|` is a literal pipe, never a delimiter
|
|
372
|
+
i += i + 1 < line_end && text.charCodeAt(i + 1) === PIPE ? 2 : 1;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (char_code === PIPE) {
|
|
376
|
+
cells.push(trim_table_cell(text, cell_start, i));
|
|
377
|
+
i++;
|
|
378
|
+
cell_start = i;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
i++;
|
|
382
|
+
}
|
|
383
|
+
// the trailing pipe is required: only whitespace may follow the last one
|
|
384
|
+
for (let k = cell_start; k < line_end; k++) {
|
|
385
|
+
if (!is_line_whitespace(text.charCodeAt(k))) return null;
|
|
386
|
+
}
|
|
387
|
+
if (cells.length === 0) return null;
|
|
388
|
+
return cells;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/** Classify a trimmed delimiter cell `[start, end)`; `false` when it is not a valid delimiter cell. */
|
|
392
|
+
const table_delimiter_align = (text: string, start: number, end: number): MdzTableAlign | false => {
|
|
393
|
+
let i = start;
|
|
394
|
+
let j = end;
|
|
395
|
+
if (i >= j) return false;
|
|
396
|
+
const left = text.charCodeAt(i) === COLON;
|
|
397
|
+
if (left) i++;
|
|
398
|
+
const right = j > i && text.charCodeAt(j - 1) === COLON;
|
|
399
|
+
if (right) j--;
|
|
400
|
+
if (i >= j) return false; // needs at least one dash
|
|
401
|
+
for (let k = i; k < j; k++) {
|
|
402
|
+
if (text.charCodeAt(k) !== HYPHEN) return false;
|
|
403
|
+
}
|
|
404
|
+
return left && right ? 'center' : left ? 'left' : right ? 'right' : null;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Parse a table delimiter row (`| --- | :-: | ---: |`) into its per-column
|
|
409
|
+
* alignment, or `null` when the line is not a valid delimiter row. Each cell
|
|
410
|
+
* must be an optional leading `:`, one or more `-`, and an optional trailing
|
|
411
|
+
* `:`. Splits with `mdz_split_table_row` so the column count matches the header.
|
|
412
|
+
*
|
|
413
|
+
* @nodocs
|
|
414
|
+
*/
|
|
415
|
+
export const mdz_parse_table_delimiter = (
|
|
416
|
+
text: string,
|
|
417
|
+
line_start: number,
|
|
418
|
+
line_end: number,
|
|
419
|
+
): Array<MdzTableAlign> | null => {
|
|
420
|
+
const cells = mdz_split_table_row(text, line_start, line_end);
|
|
421
|
+
if (cells === null) return null;
|
|
422
|
+
const align: Array<MdzTableAlign> = [];
|
|
423
|
+
for (const cell of cells) {
|
|
424
|
+
const a = table_delimiter_align(text, cell.start, cell.end);
|
|
425
|
+
if (a === false) return null;
|
|
426
|
+
align.push(a);
|
|
427
|
+
}
|
|
428
|
+
return align;
|
|
429
|
+
};
|
|
430
|
+
|
|
290
431
|
/**
|
|
291
432
|
* Lowercase an ASCII letter code; passes other chars through.
|
|
292
433
|
* Mirrors the `[A-Z] → [a-z]` shift without touching non-ASCII.
|
|
@@ -423,6 +564,40 @@ const PATH_CHAR_TABLE: Uint8Array = (() => {
|
|
|
423
564
|
export const is_valid_path_char = (char_code: number): boolean =>
|
|
424
565
|
char_code < 128 && PATH_CHAR_TABLE[char_code] === 1;
|
|
425
566
|
|
|
567
|
+
// Character classes for the plain-text run scanners (the hottest loops in
|
|
568
|
+
// both parsers) — one table load per character instead of a comparison chain.
|
|
569
|
+
// A zero class is the common case: the char can't end or interrupt a text run.
|
|
570
|
+
|
|
571
|
+
/** Always ends a text run: `` ` `` `*` `_` `~` `[` `]` `<`. @nodocs */
|
|
572
|
+
export const TEXT_CLASS_STOP = 1;
|
|
573
|
+
/** `\n` — run handling is context-dependent (soft break, paragraph break, block lookahead). @nodocs */
|
|
574
|
+
export const TEXT_CLASS_NEWLINE = 2;
|
|
575
|
+
/** `h`/`H` — potential URL scheme start, needs the prefix probe. @nodocs */
|
|
576
|
+
export const TEXT_CLASS_URL = 4;
|
|
577
|
+
/** `/` and `.` — potential path start, needs the boundary probe. @nodocs */
|
|
578
|
+
export const TEXT_CLASS_PATH = 8;
|
|
579
|
+
/** `\` — a literal-pipe escape candidate inside table cells only. @nodocs */
|
|
580
|
+
export const TEXT_CLASS_BACKSLASH = 16;
|
|
581
|
+
|
|
582
|
+
/** @nodocs */
|
|
583
|
+
export const TEXT_SCAN_CLASS: Uint8Array = (() => {
|
|
584
|
+
const t = new Uint8Array(128);
|
|
585
|
+
t[BACKTICK] = TEXT_CLASS_STOP;
|
|
586
|
+
t[ASTERISK] = TEXT_CLASS_STOP;
|
|
587
|
+
t[UNDERSCORE] = TEXT_CLASS_STOP;
|
|
588
|
+
t[TILDE] = TEXT_CLASS_STOP;
|
|
589
|
+
t[LEFT_BRACKET] = TEXT_CLASS_STOP;
|
|
590
|
+
t[RIGHT_BRACKET] = TEXT_CLASS_STOP;
|
|
591
|
+
t[LEFT_ANGLE] = TEXT_CLASS_STOP;
|
|
592
|
+
t[NEWLINE] = TEXT_CLASS_NEWLINE;
|
|
593
|
+
t[H_LOWER] = TEXT_CLASS_URL;
|
|
594
|
+
t[H_UPPER] = TEXT_CLASS_URL;
|
|
595
|
+
t[SLASH] = TEXT_CLASS_PATH;
|
|
596
|
+
t[PERIOD] = TEXT_CLASS_PATH;
|
|
597
|
+
t[BACKSLASH] = TEXT_CLASS_BACKSLASH;
|
|
598
|
+
return t;
|
|
599
|
+
})();
|
|
600
|
+
|
|
426
601
|
/**
|
|
427
602
|
* Trim trailing punctuation from URL/path per RFC 3986 and GFM rules.
|
|
428
603
|
* - Trims simple trailing: .,;:!?]
|
|
@@ -636,6 +811,52 @@ export const mdz_resolve_relative_path = (reference: string, base: string): stri
|
|
|
636
811
|
return segments.join('/');
|
|
637
812
|
};
|
|
638
813
|
|
|
814
|
+
/**
|
|
815
|
+
* How a `Link` node's `reference` should render, shared by all three renderers
|
|
816
|
+
* (`MdzNodeView`, `MdzStreamNodeView`, `mdz_to_svelte`) so the safety gate and
|
|
817
|
+
* the resolve-vs-raw classification can't drift between them.
|
|
818
|
+
* @nodocs
|
|
819
|
+
*/
|
|
820
|
+
export type MdzLinkRender =
|
|
821
|
+
/** Unsafe protocol — render children only, no `<a>`. */
|
|
822
|
+
| {kind: 'unsafe'}
|
|
823
|
+
/** Route/relative path — wrap in `resolve()` (needs `$app/paths`). */
|
|
824
|
+
| {kind: 'resolve'; href: string}
|
|
825
|
+
/** Internal fragment/query/relative/bare ref — raw `href`, no `resolve()`. */
|
|
826
|
+
| {kind: 'internal'; href: string}
|
|
827
|
+
/** External link — raw `href` plus `target="_blank" rel="noopener"`. */
|
|
828
|
+
| {kind: 'external'; href: string};
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Classify a `Link` reference into how it should render. Pure — no rendering,
|
|
832
|
+
* no context; `base` is the resolved base path (or `undefined`).
|
|
833
|
+
* @nodocs
|
|
834
|
+
*/
|
|
835
|
+
export const mdz_classify_link = (
|
|
836
|
+
reference: string,
|
|
837
|
+
link_type: 'internal' | 'external' | undefined,
|
|
838
|
+
base: string | undefined,
|
|
839
|
+
): MdzLinkRender => {
|
|
840
|
+
if (!mdz_is_safe_reference(reference)) return {kind: 'unsafe'};
|
|
841
|
+
if (link_type === 'internal') {
|
|
842
|
+
if (reference.startsWith('.') && base) {
|
|
843
|
+
return {kind: 'resolve', href: mdz_resolve_relative_path(reference, base)};
|
|
844
|
+
}
|
|
845
|
+
// fragment/query/relative/bare — `resolve()` only accepts absolute paths or
|
|
846
|
+
// route ids and throws on anything else
|
|
847
|
+
if (
|
|
848
|
+
reference.startsWith('#') ||
|
|
849
|
+
reference.startsWith('?') ||
|
|
850
|
+
reference.startsWith('.') ||
|
|
851
|
+
!reference.startsWith('/')
|
|
852
|
+
) {
|
|
853
|
+
return {kind: 'internal', href: reference};
|
|
854
|
+
}
|
|
855
|
+
return {kind: 'resolve', href: reference};
|
|
856
|
+
}
|
|
857
|
+
return {kind: 'external', href: reference};
|
|
858
|
+
};
|
|
859
|
+
|
|
639
860
|
/**
|
|
640
861
|
* Push a node into a children array, coalescing with the previous Text node.
|
|
641
862
|
* Mutates `prev.content` and `prev.end` when both are Text, avoiding array growth
|