@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
|
@@ -26,9 +26,12 @@ import type {
|
|
|
26
26
|
MdzNodeHeading,
|
|
27
27
|
MdzNodeCode,
|
|
28
28
|
MdzNodeListItem,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
MdzNodeTableRow,
|
|
30
|
+
MdzNodeTableCell,
|
|
31
|
+
MdzTableAlign,
|
|
32
|
+
} from './mdz.ts';
|
|
33
|
+
import type {MdzOpcode, MdzNodeId} from './mdz_opcodes.ts';
|
|
34
|
+
import {mdz_extract_single_tag, mdz_heading_id, mdz_merge_adjacent_text} from './mdz_helpers.ts';
|
|
32
35
|
|
|
33
36
|
interface StackFrame {
|
|
34
37
|
id: MdzNodeId;
|
|
@@ -43,6 +46,8 @@ interface StackFrame {
|
|
|
43
46
|
ordered?: boolean;
|
|
44
47
|
start_number?: number;
|
|
45
48
|
number?: number;
|
|
49
|
+
align?: Array<MdzTableAlign>;
|
|
50
|
+
header?: boolean;
|
|
46
51
|
// metadata from close opcode (deferred)
|
|
47
52
|
end?: number;
|
|
48
53
|
reference?: string;
|
|
@@ -76,6 +81,8 @@ export const mdz_opcodes_to_nodes = (opcodes: Array<MdzOpcode>): Array<MdzNode>
|
|
|
76
81
|
ordered: op.ordered,
|
|
77
82
|
start_number: op.start_number,
|
|
78
83
|
number: op.number,
|
|
84
|
+
align: op.align,
|
|
85
|
+
header: op.header,
|
|
79
86
|
});
|
|
80
87
|
break;
|
|
81
88
|
}
|
|
@@ -417,6 +424,33 @@ const build_node = (frame: StackFrame, frame_children: Array<MdzNode>): MdzNode
|
|
|
417
424
|
return {type: 'Blockquote', children: frame_children, start: frame.start, end};
|
|
418
425
|
}
|
|
419
426
|
|
|
427
|
+
case 'Table': {
|
|
428
|
+
// rows are the only children — `end` derives from the last, matching
|
|
429
|
+
// the sync token parser
|
|
430
|
+
const children = frame_children as Array<MdzNodeTableRow>;
|
|
431
|
+
const end = children.length > 0 ? children[children.length - 1]!.end : frame.end!;
|
|
432
|
+
return {type: 'Table', align: frame.align ?? [], children, start: frame.start, end};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
case 'TableRow': {
|
|
436
|
+
const children = frame_children as Array<MdzNodeTableCell>;
|
|
437
|
+
return {
|
|
438
|
+
type: 'TableRow',
|
|
439
|
+
header: frame.header ?? false,
|
|
440
|
+
children,
|
|
441
|
+
start: frame.start,
|
|
442
|
+
end: frame.end!,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
case 'TableCell':
|
|
447
|
+
return {
|
|
448
|
+
type: 'TableCell',
|
|
449
|
+
children: mdz_merge_adjacent_text(frame_children),
|
|
450
|
+
start: frame.start,
|
|
451
|
+
end: frame.end!,
|
|
452
|
+
};
|
|
453
|
+
|
|
420
454
|
case 'Code': {
|
|
421
455
|
// optimistic inline code container — concatenate children text into leaf MdzNodeCode
|
|
422
456
|
let content = '';
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* @module
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
import type {MdzOpcode} from './mdz_opcodes.
|
|
38
|
+
import type {MdzOpcode} from './mdz_opcodes.ts';
|
|
39
39
|
import {
|
|
40
40
|
BACKTICK,
|
|
41
41
|
ASTERISK,
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
NEWLINE,
|
|
45
45
|
HYPHEN,
|
|
46
46
|
HASH,
|
|
47
|
+
PIPE,
|
|
47
48
|
LEFT_ANGLE,
|
|
48
49
|
RIGHT_ANGLE,
|
|
49
50
|
SLASH,
|
|
@@ -61,7 +62,7 @@ import {
|
|
|
61
62
|
mdz_blockquote_line_has_content,
|
|
62
63
|
mdz_blockquote_strip_width,
|
|
63
64
|
mdz_remap_segments,
|
|
64
|
-
} from './mdz_helpers.
|
|
65
|
+
} from './mdz_helpers.ts';
|
|
65
66
|
import {
|
|
66
67
|
type MdzStreamParserState,
|
|
67
68
|
type TryResult,
|
|
@@ -80,7 +81,7 @@ import {
|
|
|
80
81
|
pop_stack_entry,
|
|
81
82
|
push_stack_entry,
|
|
82
83
|
revert_failed_close,
|
|
83
|
-
} from './mdz_stream_parser_state.
|
|
84
|
+
} from './mdz_stream_parser_state.ts';
|
|
84
85
|
import {
|
|
85
86
|
process_codeblock,
|
|
86
87
|
process_codeblock_forced,
|
|
@@ -88,8 +89,9 @@ import {
|
|
|
88
89
|
try_codeblock_open,
|
|
89
90
|
try_heading,
|
|
90
91
|
try_hr,
|
|
91
|
-
} from './mdz_stream_parser_block.
|
|
92
|
-
import {process_list_line, try_list_start} from './mdz_stream_parser_list.
|
|
92
|
+
} from './mdz_stream_parser_block.ts';
|
|
93
|
+
import {process_list_line, try_list_start} from './mdz_stream_parser_list.ts';
|
|
94
|
+
import {close_table, process_table_line, try_table_start} from './mdz_stream_parser_table.ts';
|
|
93
95
|
import {
|
|
94
96
|
check_close_word_boundary,
|
|
95
97
|
close_delimiter,
|
|
@@ -97,13 +99,13 @@ import {
|
|
|
97
99
|
try_code,
|
|
98
100
|
try_double_delimiter,
|
|
99
101
|
try_italic,
|
|
100
|
-
} from './mdz_stream_parser_inline.
|
|
102
|
+
} from './mdz_stream_parser_inline.ts';
|
|
101
103
|
import {
|
|
102
104
|
try_close_tag,
|
|
103
105
|
try_complete_link,
|
|
104
106
|
try_link_open,
|
|
105
107
|
try_tag_open,
|
|
106
|
-
} from './mdz_stream_parser_link.
|
|
108
|
+
} from './mdz_stream_parser_link.ts';
|
|
107
109
|
import {
|
|
108
110
|
complete_pending_url,
|
|
109
111
|
process_url_content,
|
|
@@ -111,8 +113,8 @@ import {
|
|
|
111
113
|
try_auto_path_absolute,
|
|
112
114
|
try_auto_path_relative,
|
|
113
115
|
try_auto_url_forced,
|
|
114
|
-
} from './mdz_stream_parser_url.
|
|
115
|
-
import {consume_delimiter_as_text, consume_text_run} from './mdz_stream_parser_text.
|
|
116
|
+
} from './mdz_stream_parser_url.ts';
|
|
117
|
+
import {consume_delimiter_as_text, consume_text_run} from './mdz_stream_parser_text.ts';
|
|
116
118
|
|
|
117
119
|
/**
|
|
118
120
|
* Streaming opcode parser for mdz content.
|
|
@@ -224,6 +226,11 @@ const finish_state = (state: MdzStreamParserState): void => {
|
|
|
224
226
|
if (state.buffer.length > 0) {
|
|
225
227
|
process_loop(state, true);
|
|
226
228
|
}
|
|
229
|
+
// close an open top-level table — forced processing ends it at the last
|
|
230
|
+
// row, but an already-drained buffer (everything consumed) needs this net
|
|
231
|
+
if (state.table) {
|
|
232
|
+
close_table(state);
|
|
233
|
+
}
|
|
227
234
|
// close an open blockquote — finishes its nested inner document (an
|
|
228
235
|
// unclosed fence inside ends at the quote's EOF, which is this) and
|
|
229
236
|
// cascades recursively through deeper quotes
|
|
@@ -558,6 +565,12 @@ const process_loop = (state: MdzStreamParserState, forced: boolean): void => {
|
|
|
558
565
|
continue;
|
|
559
566
|
}
|
|
560
567
|
|
|
568
|
+
// open table — each line is a body row or ends the table
|
|
569
|
+
if (state.table) {
|
|
570
|
+
if (!process_table_line(state, forced)) return; // need more input
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
|
|
561
574
|
const char_code = state.buffer.charCodeAt(state.pos);
|
|
562
575
|
|
|
563
576
|
// newline handling
|
|
@@ -632,6 +645,10 @@ const process_loop = (state: MdzStreamParserState, forced: boolean): void => {
|
|
|
632
645
|
const r = try_blockquote_start(state, forced);
|
|
633
646
|
if (r === 'consumed') continue;
|
|
634
647
|
if (!forced && r === 'need_more') return;
|
|
648
|
+
} else if (char_code === PIPE) {
|
|
649
|
+
const r = try_table_start(state, forced);
|
|
650
|
+
if (r === 'consumed') continue;
|
|
651
|
+
if (!forced && r === 'need_more') return;
|
|
635
652
|
}
|
|
636
653
|
}
|
|
637
654
|
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
NEWLINE,
|
|
18
18
|
SPACE,
|
|
19
19
|
is_line_whitespace,
|
|
20
|
-
} from './mdz_helpers.
|
|
20
|
+
} from './mdz_helpers.ts';
|
|
21
21
|
import {
|
|
22
22
|
type CodeblockState,
|
|
23
23
|
type MdzStreamParserState,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
flush_text,
|
|
29
29
|
offset,
|
|
30
30
|
push_stack_entry,
|
|
31
|
-
} from './mdz_stream_parser_state.
|
|
31
|
+
} from './mdz_stream_parser_state.ts';
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Try to parse a heading at column 0.
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
is_word_char,
|
|
15
15
|
mdz_blockquote_line_has_content,
|
|
16
16
|
mdz_match_blockquote_prefix,
|
|
17
|
-
} from './mdz_helpers.
|
|
17
|
+
} from './mdz_helpers.ts';
|
|
18
18
|
import {
|
|
19
19
|
type MdzStreamParserState,
|
|
20
20
|
accumulate_text,
|
|
@@ -28,9 +28,9 @@ import {
|
|
|
28
28
|
pop_stack_entry,
|
|
29
29
|
push_stack_entry,
|
|
30
30
|
revert_above,
|
|
31
|
-
} from './mdz_stream_parser_state.
|
|
32
|
-
import {match_fence_opener, match_list_marker} from './mdz_stream_parser_list.
|
|
33
|
-
import {consume_delimiter_as_text} from './mdz_stream_parser_text.
|
|
31
|
+
} from './mdz_stream_parser_state.ts';
|
|
32
|
+
import {match_fence_opener, match_list_marker} from './mdz_stream_parser_list.ts';
|
|
33
|
+
import {consume_delimiter_as_text} from './mdz_stream_parser_text.ts';
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Whether `buffer` contains a paragraph break — a newline followed (across
|
|
@@ -339,17 +339,24 @@ export const scan_closer_boundary = (
|
|
|
339
339
|
/** @nodocs */
|
|
340
340
|
export const try_code = (state: MdzStreamParserState, forced = false): boolean => {
|
|
341
341
|
const start = state.pos;
|
|
342
|
-
|
|
342
|
+
const search_start = start + 1; // past opening backtick
|
|
343
343
|
|
|
344
344
|
// compute search boundary: don't scan past open formatting delimiters
|
|
345
|
-
const search_limit = code_search_limit(state,
|
|
345
|
+
const search_limit = code_search_limit(state, search_start);
|
|
346
346
|
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
347
|
+
// find the first closing backtick or newline (memoized — a candidate that
|
|
348
|
+
// holds under open formatting re-enters here on every feed, and a raw scan
|
|
349
|
+
// would re-cover the same growing region each time, going quadratic on a
|
|
350
|
+
// single long line)
|
|
351
|
+
const close_pos = buffer_index_of(state, '`', search_start);
|
|
352
|
+
const newline_pos = buffer_index_of(state, '\n', search_start);
|
|
353
|
+
const terminator =
|
|
354
|
+
close_pos !== -1 && (newline_pos === -1 || close_pos < newline_pos) ? close_pos : newline_pos;
|
|
355
|
+
|
|
356
|
+
if (terminator !== -1 && terminator < search_limit) {
|
|
357
|
+
if (terminator === close_pos) {
|
|
351
358
|
// found close
|
|
352
|
-
const content = state.buffer.slice(
|
|
359
|
+
const content = state.buffer.slice(search_start, terminator);
|
|
353
360
|
if (content.length === 0) {
|
|
354
361
|
// empty code — treat as text
|
|
355
362
|
consume_delimiter_as_text(state, '``');
|
|
@@ -364,20 +371,17 @@ export const try_code = (state: MdzStreamParserState, forced = false): boolean =
|
|
|
364
371
|
content,
|
|
365
372
|
text_type: 'Code',
|
|
366
373
|
start: offset(state, start),
|
|
367
|
-
end: offset(state,
|
|
374
|
+
end: offset(state, terminator + 1),
|
|
368
375
|
});
|
|
369
376
|
state.active_text_id = null;
|
|
370
|
-
state.pos =
|
|
371
|
-
state.column +=
|
|
377
|
+
state.pos = terminator + 1;
|
|
378
|
+
state.column += terminator + 1 - start;
|
|
372
379
|
state.prev_char = BACKTICK;
|
|
373
380
|
return true;
|
|
374
381
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
i++;
|
|
382
|
+
// newline before any close — inline code can't span lines
|
|
383
|
+
consume_delimiter_as_text(state, '`');
|
|
384
|
+
return true;
|
|
381
385
|
}
|
|
382
386
|
|
|
383
387
|
// hit search limit (open formatting delimiter boundary) — treat backtick as
|
|
@@ -386,7 +390,7 @@ export const try_code = (state: MdzStreamParserState, forced = false): boolean =
|
|
|
386
390
|
// processed), the one-shot parse rejects that italic and scans unbounded —
|
|
387
391
|
// see the `MdzStreamParser` TSDoc and the parity suite's expected-divergence
|
|
388
392
|
// battery
|
|
389
|
-
if (
|
|
393
|
+
if (search_limit < state.buffer.length) {
|
|
390
394
|
consume_delimiter_as_text(state, '`');
|
|
391
395
|
return true;
|
|
392
396
|
}
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {MdzNodeTypeContainer} from './mdz_opcodes.
|
|
8
|
+
import type {MdzNodeTypeContainer} from './mdz_opcodes.ts';
|
|
9
9
|
import {
|
|
10
10
|
A_UPPER,
|
|
11
11
|
LEFT_BRACKET,
|
|
12
12
|
LEFT_PAREN,
|
|
13
|
-
|
|
13
|
+
MAX_INLINE_NESTING_DEPTH,
|
|
14
14
|
RIGHT_ANGLE,
|
|
15
15
|
RIGHT_BRACKET,
|
|
16
16
|
RIGHT_PAREN,
|
|
@@ -22,12 +22,13 @@ import {
|
|
|
22
22
|
is_valid_path_char,
|
|
23
23
|
mdz_is_safe_reference,
|
|
24
24
|
mdz_is_url,
|
|
25
|
-
} from './mdz_helpers.
|
|
25
|
+
} from './mdz_helpers.ts';
|
|
26
26
|
import {
|
|
27
27
|
type MdzStreamParserState,
|
|
28
28
|
type TryResult,
|
|
29
29
|
accumulate_text,
|
|
30
30
|
alloc_id,
|
|
31
|
+
buffer_index_of,
|
|
31
32
|
emit,
|
|
32
33
|
ensure_paragraph,
|
|
33
34
|
flush_text,
|
|
@@ -36,12 +37,21 @@ import {
|
|
|
36
37
|
pop_stack_entry,
|
|
37
38
|
push_stack_entry,
|
|
38
39
|
revert_above,
|
|
39
|
-
} from './mdz_stream_parser_state.
|
|
40
|
+
} from './mdz_stream_parser_state.ts';
|
|
41
|
+
import {consume_delimiter_as_text} from './mdz_stream_parser_text.ts';
|
|
42
|
+
import {mdz_debug_work} from './mdz_debug_work.ts';
|
|
40
43
|
|
|
41
44
|
// -- Links --
|
|
42
45
|
|
|
43
46
|
/** @nodocs */
|
|
44
47
|
export const try_link_open = (state: MdzStreamParserState): boolean => {
|
|
48
|
+
// nesting-depth cap: too deep to open — render `[` literal, matching the
|
|
49
|
+
// sync lexer's `#tokenize_markdown_link` cap (keeps the two parsers agreeing
|
|
50
|
+
// on where deep `[` stops nesting)
|
|
51
|
+
if (state.open_link_tag_depth >= MAX_INLINE_NESTING_DEPTH) {
|
|
52
|
+
consume_delimiter_as_text(state, '[');
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
45
55
|
flush_text(state);
|
|
46
56
|
ensure_paragraph(state);
|
|
47
57
|
|
|
@@ -97,65 +107,69 @@ export const try_complete_link = (state: MdzStreamParserState, link_stack_idx: n
|
|
|
97
107
|
return true;
|
|
98
108
|
}
|
|
99
109
|
|
|
100
|
-
// found ]( —
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
end: offset(state, i + 1),
|
|
138
|
-
reference,
|
|
139
|
-
link_type,
|
|
140
|
-
});
|
|
141
|
-
state.active_text_id = null;
|
|
142
|
-
state.pos = i + 1;
|
|
143
|
-
state.column += i + 1 - bracket_pos;
|
|
144
|
-
state.prev_char = RIGHT_PAREN;
|
|
110
|
+
// found ]( — find the reference's terminator: `)` completes, a space or
|
|
111
|
+
// newline aborts. Memoized searches (see `buffer_index_of`) — a link whose
|
|
112
|
+
// URL arrives incrementally re-enters here on every feed with `pos` still
|
|
113
|
+
// at the `]`, and a raw scan would re-cover the growing reference each
|
|
114
|
+
// time, going quadratic on long streamed URLs.
|
|
115
|
+
const ref_start = bracket_pos + 2;
|
|
116
|
+
const close_pos = buffer_index_of(state, ')', ref_start);
|
|
117
|
+
const space_pos = buffer_index_of(state, ' ', ref_start);
|
|
118
|
+
const newline_pos = buffer_index_of(state, '\n', ref_start);
|
|
119
|
+
let invalid_pos = space_pos;
|
|
120
|
+
if (newline_pos !== -1 && (invalid_pos === -1 || newline_pos < invalid_pos)) {
|
|
121
|
+
invalid_pos = newline_pos;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (close_pos === -1) {
|
|
125
|
+
// didn't find ) — need more input or invalid
|
|
126
|
+
if (invalid_pos === -1) return false; // need more input
|
|
127
|
+
// found invalid char before any ) — revert
|
|
128
|
+
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (invalid_pos !== -1 && invalid_pos < close_pos) {
|
|
132
|
+
// invalid character in URL (simplified check)
|
|
133
|
+
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const i = close_pos;
|
|
138
|
+
const reference = state.buffer.slice(ref_start, i);
|
|
139
|
+
// validate reference: non-empty and only valid path chars
|
|
140
|
+
if (!reference.trim()) {
|
|
141
|
+
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
for (let j = 0; j < reference.length; j++) {
|
|
145
|
+
if (!is_valid_path_char(reference.charCodeAt(j))) {
|
|
146
|
+
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
145
147
|
return true;
|
|
146
148
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
}
|
|
150
|
+
// Reject unsafe protocols (javascript:, data:, etc.) at parse time.
|
|
151
|
+
// `is_valid_path_char` permits `:` so we have to filter explicitly.
|
|
152
|
+
if (!mdz_is_safe_reference(reference)) {
|
|
153
|
+
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
154
|
+
return true;
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
// success — close the link with reference
|
|
158
|
+
flush_text(state);
|
|
159
|
+
revert_above(state, link_stack_idx);
|
|
160
|
+
const entry = pop_stack_entry(state);
|
|
161
|
+
const link_type = mdz_is_url(reference) ? 'external' : 'internal';
|
|
162
|
+
emit(state, {
|
|
163
|
+
type: 'close',
|
|
164
|
+
id: entry.id,
|
|
165
|
+
end: offset(state, i + 1),
|
|
166
|
+
reference,
|
|
167
|
+
link_type,
|
|
168
|
+
});
|
|
169
|
+
state.active_text_id = null;
|
|
170
|
+
state.pos = i + 1;
|
|
171
|
+
state.column += i + 1 - bracket_pos;
|
|
172
|
+
state.prev_char = RIGHT_PAREN;
|
|
159
173
|
return true;
|
|
160
174
|
};
|
|
161
175
|
|
|
@@ -176,7 +190,19 @@ export const try_tag_open = (state: MdzStreamParserState): TryResult => {
|
|
|
176
190
|
return 'not_match';
|
|
177
191
|
}
|
|
178
192
|
|
|
193
|
+
// nesting-depth cap: `<letter…` is a tag open, but we're too deep to nest —
|
|
194
|
+
// render `<` literal, matching the sync lexer's `#tokenize_tag` cap. Checked
|
|
195
|
+
// after the closer/hold cases above (a `</…>` closer still closes; a `<` at
|
|
196
|
+
// the buffer end still holds), so only genuine opens are suppressed.
|
|
197
|
+
if (state.open_link_tag_depth >= MAX_INLINE_NESTING_DEPTH) {
|
|
198
|
+
consume_delimiter_as_text(state, '<');
|
|
199
|
+
return 'consumed';
|
|
200
|
+
}
|
|
201
|
+
|
|
179
202
|
// collect tag name
|
|
203
|
+
// TODO the name/whitespace scans rescan from `start` on every held retry
|
|
204
|
+
// of an unclosed tag's open tail — needs a hold watermark; a length cap would
|
|
205
|
+
// diverge from the sync parser on pathological multi-KB "tag names"
|
|
180
206
|
const name_start = i;
|
|
181
207
|
while (i < state.buffer.length && is_tag_name_char(state.buffer.charCodeAt(i))) {
|
|
182
208
|
i++;
|
|
@@ -255,9 +281,16 @@ export const try_close_tag = (state: MdzStreamParserState): TryResult => {
|
|
|
255
281
|
const name = state.buffer.slice(name_start, i);
|
|
256
282
|
i++; // past >
|
|
257
283
|
|
|
284
|
+
// O(1) bail when no tag of this name is open — without it, every `</name>`
|
|
285
|
+
// candidate walks the whole inline stack, which grows one frame per leaked
|
|
286
|
+
// unclosed `<Tag>` and goes quadratic on tag-dense input (the same shape
|
|
287
|
+
// `open_counts` guards for the delimiter inlines)
|
|
288
|
+
if (!state.open_tag_counts?.get(name)) return 'not_match';
|
|
289
|
+
|
|
258
290
|
// find matching open tag on stack
|
|
259
291
|
let found_idx = -1;
|
|
260
292
|
for (let j = state.stack.length - 1; j >= 0; j--) {
|
|
293
|
+
if (mdz_debug_work.enabled) mdz_debug_work.total++; // frames walked (mismatched-tags guard)
|
|
261
294
|
const entry = state.stack[j]!;
|
|
262
295
|
if (
|
|
263
296
|
(entry.node_type === 'Element' || entry.node_type === 'Component') &&
|
|
@@ -21,13 +21,15 @@ import {
|
|
|
21
21
|
NEWLINE,
|
|
22
22
|
NINE,
|
|
23
23
|
PERIOD,
|
|
24
|
+
PIPE,
|
|
24
25
|
RIGHT_ANGLE,
|
|
25
26
|
SPACE,
|
|
26
27
|
ZERO,
|
|
27
28
|
is_line_whitespace,
|
|
28
29
|
mdz_match_blockquote_prefix,
|
|
29
30
|
mdz_blockquote_line_has_content,
|
|
30
|
-
} from './mdz_helpers.
|
|
31
|
+
} from './mdz_helpers.ts';
|
|
32
|
+
import {match_table_in_item_head, open_table_in_item} from './mdz_stream_parser_table.ts';
|
|
31
33
|
import {
|
|
32
34
|
type MdzStreamParserState,
|
|
33
35
|
type TryResult,
|
|
@@ -43,7 +45,7 @@ import {
|
|
|
43
45
|
flush_text,
|
|
44
46
|
offset,
|
|
45
47
|
push_stack_entry,
|
|
46
|
-
} from './mdz_stream_parser_state.
|
|
48
|
+
} from './mdz_stream_parser_state.ts';
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
51
|
* A matched list marker: `- ` (unordered) or `N. ` (ordered, 1-9 digits).
|
|
@@ -416,10 +418,16 @@ export const process_list_line = (
|
|
|
416
418
|
|
|
417
419
|
if (marker !== null && marker.empty) {
|
|
418
420
|
// empty item: only at an indent exactly matching an open level of the
|
|
419
|
-
// same marker type — anything else degrades below
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
)
|
|
421
|
+
// same marker type — anything else degrades below (manual loop like
|
|
422
|
+
// `list_continuation_target` — no per-line closure allocation)
|
|
423
|
+
let level_idx = -1;
|
|
424
|
+
for (let li = 0; li < state.list_levels.length; li++) {
|
|
425
|
+
const l = state.list_levels[li]!;
|
|
426
|
+
if (l.indent === indent && l.ordered === marker.ordered) {
|
|
427
|
+
level_idx = li;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
423
431
|
if (level_idx !== -1) {
|
|
424
432
|
close_list_run(state);
|
|
425
433
|
while (state.list_levels.length - 1 > level_idx) {
|
|
@@ -495,6 +503,26 @@ export const process_list_line = (
|
|
|
495
503
|
}
|
|
496
504
|
}
|
|
497
505
|
|
|
506
|
+
// a deeper pipe-row line whose next line is a delimiter becomes a table
|
|
507
|
+
// child of the item it would otherwise continue (the fence/quote precedent,
|
|
508
|
+
// a two-line lookahead held until the delimiter line arrives)
|
|
509
|
+
if (indent > 0 && buffer.charCodeAt(j) === PIPE) {
|
|
510
|
+
const target_idx = list_continuation_target(state, indent);
|
|
511
|
+
if (target_idx !== -1) {
|
|
512
|
+
const marker_indent = state.list_levels[target_idx]!.indent;
|
|
513
|
+
const head = match_table_in_item_head(state, j, marker_indent, forced);
|
|
514
|
+
if (head === 'pending') return false;
|
|
515
|
+
if (head !== null) {
|
|
516
|
+
close_list_run(state);
|
|
517
|
+
while (state.list_levels.length - 1 > target_idx) {
|
|
518
|
+
close_list_level(state);
|
|
519
|
+
}
|
|
520
|
+
open_table_in_item(state, j, marker_indent, head);
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
498
526
|
if (run_current && indent > deepest.indent) {
|
|
499
527
|
// continuation line: soft break, structural indent stripped
|
|
500
528
|
return continue_run(state, nl_pos, j);
|