@fuzdev/mdz 0.1.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/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/Mdz.svelte +39 -0
- package/dist/Mdz.svelte.d.ts +17 -0
- package/dist/Mdz.svelte.d.ts.map +1 -0
- package/dist/MdzNodeView.svelte +140 -0
- package/dist/MdzNodeView.svelte.d.ts +9 -0
- package/dist/MdzNodeView.svelte.d.ts.map +1 -0
- package/dist/MdzPrecompiled.svelte +33 -0
- package/dist/MdzPrecompiled.svelte.d.ts +17 -0
- package/dist/MdzPrecompiled.svelte.d.ts.map +1 -0
- package/dist/MdzRoot.svelte +40 -0
- package/dist/MdzRoot.svelte.d.ts +14 -0
- package/dist/MdzRoot.svelte.d.ts.map +1 -0
- package/dist/MdzStream.svelte +37 -0
- package/dist/MdzStream.svelte.d.ts +18 -0
- package/dist/MdzStream.svelte.d.ts.map +1 -0
- package/dist/MdzStreamNodeView.svelte +150 -0
- package/dist/MdzStreamNodeView.svelte.d.ts +9 -0
- package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -0
- package/dist/context_helpers.d.ts +23 -0
- package/dist/context_helpers.d.ts.map +1 -0
- package/dist/context_helpers.js +19 -0
- package/dist/mdz.d.ts +152 -0
- package/dist/mdz.d.ts.map +1 -0
- package/dist/mdz.js +61 -0
- package/dist/mdz_contexts.d.ts +106 -0
- package/dist/mdz_contexts.d.ts.map +1 -0
- package/dist/mdz_contexts.js +42 -0
- package/dist/mdz_helpers.d.ts +321 -0
- package/dist/mdz_helpers.d.ts.map +1 -0
- package/dist/mdz_helpers.js +668 -0
- package/dist/mdz_lexer.d.ts +123 -0
- package/dist/mdz_lexer.d.ts.map +1 -0
- package/dist/mdz_lexer.js +1405 -0
- package/dist/mdz_opcodes.d.ts +193 -0
- package/dist/mdz_opcodes.d.ts.map +1 -0
- package/dist/mdz_opcodes.js +14 -0
- package/dist/mdz_opcodes_to_nodes.d.ts +26 -0
- package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -0
- package/dist/mdz_opcodes_to_nodes.js +410 -0
- package/dist/mdz_stream_parser.d.ts +109 -0
- package/dist/mdz_stream_parser.d.ts.map +1 -0
- package/dist/mdz_stream_parser.js +699 -0
- package/dist/mdz_stream_parser_block.d.ts +99 -0
- package/dist/mdz_stream_parser_block.d.ts.map +1 -0
- package/dist/mdz_stream_parser_block.js +437 -0
- package/dist/mdz_stream_parser_inline.d.ts +98 -0
- package/dist/mdz_stream_parser_inline.d.ts.map +1 -0
- package/dist/mdz_stream_parser_inline.js +481 -0
- package/dist/mdz_stream_parser_link.d.ts +29 -0
- package/dist/mdz_stream_parser_link.d.ts.map +1 -0
- package/dist/mdz_stream_parser_link.js +227 -0
- package/dist/mdz_stream_parser_list.d.ts +106 -0
- package/dist/mdz_stream_parser_list.d.ts.map +1 -0
- package/dist/mdz_stream_parser_list.js +438 -0
- package/dist/mdz_stream_parser_state.d.ts +476 -0
- package/dist/mdz_stream_parser_state.d.ts.map +1 -0
- package/dist/mdz_stream_parser_state.js +651 -0
- package/dist/mdz_stream_parser_text.d.ts +25 -0
- package/dist/mdz_stream_parser_text.d.ts.map +1 -0
- package/dist/mdz_stream_parser_text.js +67 -0
- package/dist/mdz_stream_parser_url.d.ts +76 -0
- package/dist/mdz_stream_parser_url.d.ts.map +1 -0
- package/dist/mdz_stream_parser_url.js +328 -0
- package/dist/mdz_stream_state.svelte.d.ts +53 -0
- package/dist/mdz_stream_state.svelte.d.ts.map +1 -0
- package/dist/mdz_stream_state.svelte.js +360 -0
- package/dist/mdz_to_svelte.d.ts +71 -0
- package/dist/mdz_to_svelte.d.ts.map +1 -0
- package/dist/mdz_to_svelte.js +127 -0
- package/dist/mdz_token_parser.d.ts +20 -0
- package/dist/mdz_token_parser.d.ts.map +1 -0
- package/dist/mdz_token_parser.js +449 -0
- package/dist/svelte_preprocess_mdz.d.ts +80 -0
- package/dist/svelte_preprocess_mdz.d.ts.map +1 -0
- package/dist/svelte_preprocess_mdz.js +543 -0
- package/dist/tsdoc_mdz.d.ts +45 -0
- package/dist/tsdoc_mdz.d.ts.map +1 -0
- package/dist/tsdoc_mdz.js +88 -0
- package/package.json +131 -0
- package/src/lib/context_helpers.ts +52 -0
- package/src/lib/mdz.ts +190 -0
- package/src/lib/mdz_contexts.ts +99 -0
- package/src/lib/mdz_helpers.ts +727 -0
- package/src/lib/mdz_lexer.ts +1761 -0
- package/src/lib/mdz_opcodes.ts +227 -0
- package/src/lib/mdz_opcodes_to_nodes.ts +470 -0
- package/src/lib/mdz_stream_parser.ts +766 -0
- package/src/lib/mdz_stream_parser_block.ts +490 -0
- package/src/lib/mdz_stream_parser_inline.ts +527 -0
- package/src/lib/mdz_stream_parser_link.ts +283 -0
- package/src/lib/mdz_stream_parser_list.ts +525 -0
- package/src/lib/mdz_stream_parser_state.ts +957 -0
- package/src/lib/mdz_stream_parser_text.ts +95 -0
- package/src/lib/mdz_stream_parser_url.ts +386 -0
- package/src/lib/mdz_stream_state.svelte.ts +383 -0
- package/src/lib/mdz_to_svelte.ts +213 -0
- package/src/lib/mdz_token_parser.ts +544 -0
- package/src/lib/svelte_preprocess_mdz.ts +777 -0
- package/src/lib/tsdoc_mdz.ts +97 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block-element handlers for the streaming mdz parser.
|
|
3
|
+
*
|
|
4
|
+
* Each `try_*` returns {@link TryResult} so the orchestrator in
|
|
5
|
+
* {@link ./mdz_stream_parser.ts} can fall through or wait for more input.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { type CodeblockState, type MdzStreamParserState, type TryResult } from './mdz_stream_parser_state.js';
|
|
10
|
+
/**
|
|
11
|
+
* Try to parse a heading at column 0.
|
|
12
|
+
*
|
|
13
|
+
* @nodocs
|
|
14
|
+
*/
|
|
15
|
+
export declare const try_heading: (state: MdzStreamParserState) => TryResult;
|
|
16
|
+
/**
|
|
17
|
+
* Try to parse a horizontal rule at column 0.
|
|
18
|
+
* When `forced` is true, treats buffer end as EOF (no more input coming).
|
|
19
|
+
*
|
|
20
|
+
* @nodocs
|
|
21
|
+
*/
|
|
22
|
+
export declare const try_hr: (state: MdzStreamParserState, forced?: boolean) => TryResult;
|
|
23
|
+
/**
|
|
24
|
+
* Try to open a code block at column 0. A complete opener line is decisive —
|
|
25
|
+
* raw mode is absolute, so fence-ness never depends on a closer arriving:
|
|
26
|
+
* an unclosed fence consumes to EOF as content. Any open paragraph closes
|
|
27
|
+
* first (fences interrupt paragraphs, matching the sync parser). The only
|
|
28
|
+
* hold is the opener line itself; an opener cut off at EOF without its
|
|
29
|
+
* newline stays text (the orchestrator treats `need_more` as text in forced
|
|
30
|
+
* mode).
|
|
31
|
+
*
|
|
32
|
+
* @nodocs
|
|
33
|
+
*/
|
|
34
|
+
export declare const try_codeblock_open: (state: MdzStreamParserState) => TryResult;
|
|
35
|
+
/**
|
|
36
|
+
* Emit codeblock content as `text` (first chunk) or `append_text` (subsequent
|
|
37
|
+
* chunks), updating `cb.text_id` on first emit. `content_start` is the local
|
|
38
|
+
* buffer position where the content slice began — only used to compute the
|
|
39
|
+
* global byte offset for the first `text` opcode.
|
|
40
|
+
*
|
|
41
|
+
* @nodocs
|
|
42
|
+
*/
|
|
43
|
+
export declare const emit_codeblock_text: (state: MdzStreamParserState, cb: CodeblockState, content: string, content_start: number) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Drain the remaining buffer as codeblock content at EOF, looking for a closing
|
|
46
|
+
* fence whose terminator is EOF rather than `\n`. Matches the sync lexer's
|
|
47
|
+
* `#match_fence`, which allows a fence at the end of the document.
|
|
48
|
+
*
|
|
49
|
+
* If a fence is found, the codeblock closes normally. Otherwise the remaining
|
|
50
|
+
* buffer becomes content and `close_codeblock_at_eof` (in
|
|
51
|
+
* `mdz_stream_parser_state.ts`) emits the plain close after process completes
|
|
52
|
+
* (an unclosed fence consumes to EOF — never reverted).
|
|
53
|
+
*
|
|
54
|
+
* @nodocs
|
|
55
|
+
*/
|
|
56
|
+
export declare const process_codeblock_forced: (state: MdzStreamParserState) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Process bytes while in codeblock mode.
|
|
59
|
+
* Returns true if processing should continue, false if more input needed.
|
|
60
|
+
*
|
|
61
|
+
* @nodocs
|
|
62
|
+
*/
|
|
63
|
+
export declare const process_codeblock: (state: MdzStreamParserState) => boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Process buffered lines while inside an in-item code block
|
|
66
|
+
* (`fence_indent > 0`). Operates on complete lines only — a partial line at
|
|
67
|
+
* the buffer end holds until its newline arrives (line-bounded, like the
|
|
68
|
+
* opener hold), keeping the per-line indent strip and closer probe
|
|
69
|
+
* deterministic across chunk boundaries. Content lines strip
|
|
70
|
+
* `min(fence_indent, line_indent)` and emit joined by `'\n'` separators
|
|
71
|
+
* (first line bare, later lines prefixed), so emitted content never carries
|
|
72
|
+
* the region's final newline — `close_codeblock_at_eof` therefore skips its
|
|
73
|
+
* trailing-newline trim for in-item fences.
|
|
74
|
+
*
|
|
75
|
+
* A closing fence — any indent <= the opener's, a run of >= the opener's
|
|
76
|
+
* backticks, only whitespace to the line end — closes the block with `pos`
|
|
77
|
+
* left at its terminating newline, so the list line classifier dispatches
|
|
78
|
+
* what follows (mirroring the sync lexer's loop shape). In forced mode EOF
|
|
79
|
+
* terminates closer and content lines.
|
|
80
|
+
*
|
|
81
|
+
* Returns `false` when more input is needed; never in forced mode.
|
|
82
|
+
*
|
|
83
|
+
* @nodocs
|
|
84
|
+
*/
|
|
85
|
+
export declare const process_codeblock_in_item: (state: MdzStreamParserState, forced: boolean) => boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Check if the buffer at current position has a codeblock closing fence —
|
|
88
|
+
* a run of at least `backtick_count` backticks with only whitespace to end
|
|
89
|
+
* of line. Returns the position after the fence (including trailing newline)
|
|
90
|
+
* on success, `-1` when definitely not a fence, or `-2` when more input is
|
|
91
|
+
* needed to decide.
|
|
92
|
+
*
|
|
93
|
+
* When `forced` is true, EOF after the fence counts as a valid terminator
|
|
94
|
+
* (matches `mdz_parse`'s handling of code blocks at end of input).
|
|
95
|
+
*
|
|
96
|
+
* @nodocs
|
|
97
|
+
*/
|
|
98
|
+
export declare const match_codeblock_close: (state: MdzStreamParserState, backtick_count: number, forced?: boolean) => number;
|
|
99
|
+
//# sourceMappingURL=mdz_stream_parser_block.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdz_stream_parser_block.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_block.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EAOd,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,oBAAoB,KAAG,SAuDzD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,oBAAoB,EAAE,gBAAc,KAAG,SA4CpE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,oBAAoB,KAAG,SAgDhE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,OAAO,oBAAoB,EAC3B,IAAI,cAAc,EAClB,SAAS,MAAM,EACf,eAAe,MAAM,KACnB,IAgBF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,oBAAoB,KAAG,IA6CtE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,oBAAoB,KAAG,OA2D/D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,yBAAyB,GACrC,OAAO,oBAAoB,EAC3B,QAAQ,OAAO,KACb,OAmDF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,oBAAoB,EAC3B,gBAAgB,MAAM,EACtB,SAAQ,OAAe,KACrB,MAgCF,CAAC"}
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block-element handlers for the streaming mdz parser.
|
|
3
|
+
*
|
|
4
|
+
* Each `try_*` returns {@link TryResult} so the orchestrator in
|
|
5
|
+
* {@link ./mdz_stream_parser.ts} can fall through or wait for more input.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { BACKTICK, HASH, HR_HYPHEN_COUNT, HYPHEN, MAX_HEADING_LEVEL, MIN_CODEBLOCK_BACKTICKS, NEWLINE, SPACE, is_line_whitespace, } from './mdz_helpers.js';
|
|
10
|
+
import { alloc_id, close_paragraph, emit, flush_text, offset, push_stack_entry, } from './mdz_stream_parser_state.js';
|
|
11
|
+
/**
|
|
12
|
+
* Try to parse a heading at column 0.
|
|
13
|
+
*
|
|
14
|
+
* @nodocs
|
|
15
|
+
*/
|
|
16
|
+
export const try_heading = (state) => {
|
|
17
|
+
const start = state.pos;
|
|
18
|
+
let i = start;
|
|
19
|
+
// count hashes
|
|
20
|
+
let hash_count = 0;
|
|
21
|
+
while (i < state.buffer.length &&
|
|
22
|
+
state.buffer.charCodeAt(i) === HASH &&
|
|
23
|
+
hash_count <= MAX_HEADING_LEVEL) {
|
|
24
|
+
hash_count++;
|
|
25
|
+
i++;
|
|
26
|
+
}
|
|
27
|
+
if (hash_count === 0 || hash_count > MAX_HEADING_LEVEL)
|
|
28
|
+
return 'not_match';
|
|
29
|
+
// need at least one more char to check for space
|
|
30
|
+
if (i >= state.buffer.length)
|
|
31
|
+
return 'need_more';
|
|
32
|
+
// must have space after hashes
|
|
33
|
+
if (state.buffer.charCodeAt(i) !== SPACE)
|
|
34
|
+
return 'not_match';
|
|
35
|
+
i++; // consume space
|
|
36
|
+
// must have at least one non-whitespace char before the newline (mirrors
|
|
37
|
+
// the sync lexer's has-content probe) — hold until it can be decided. A
|
|
38
|
+
// line-bounded hold: the heading opens the moment its first content char
|
|
39
|
+
// arrives. In forced mode the orchestrator treats `need_more` as text.
|
|
40
|
+
let j = i;
|
|
41
|
+
while (j < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(j))) {
|
|
42
|
+
j++;
|
|
43
|
+
}
|
|
44
|
+
if (j >= state.buffer.length)
|
|
45
|
+
return 'need_more';
|
|
46
|
+
if (state.buffer.charCodeAt(j) === NEWLINE)
|
|
47
|
+
return 'not_match'; // no content — literal text
|
|
48
|
+
// close any open paragraph first
|
|
49
|
+
flush_text(state);
|
|
50
|
+
close_paragraph(state);
|
|
51
|
+
const id = alloc_id(state);
|
|
52
|
+
const heading_start = offset(state, start);
|
|
53
|
+
emit(state, {
|
|
54
|
+
type: 'open',
|
|
55
|
+
id,
|
|
56
|
+
node_type: 'Heading',
|
|
57
|
+
start: heading_start,
|
|
58
|
+
level: hash_count,
|
|
59
|
+
});
|
|
60
|
+
push_stack_entry(state, id, 'Heading', heading_start);
|
|
61
|
+
state.in_heading = true;
|
|
62
|
+
state.heading_text_parts = [''];
|
|
63
|
+
state.pos = i;
|
|
64
|
+
state.column = i - start;
|
|
65
|
+
state.prev_char = SPACE;
|
|
66
|
+
return 'consumed';
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Try to parse a horizontal rule at column 0.
|
|
70
|
+
* When `forced` is true, treats buffer end as EOF (no more input coming).
|
|
71
|
+
*
|
|
72
|
+
* @nodocs
|
|
73
|
+
*/
|
|
74
|
+
export const try_hr = (state, forced = false) => {
|
|
75
|
+
const start = state.pos;
|
|
76
|
+
let i = start;
|
|
77
|
+
// must have exactly 3 hyphens
|
|
78
|
+
if (i + HR_HYPHEN_COUNT > state.buffer.length)
|
|
79
|
+
return 'need_more';
|
|
80
|
+
for (let j = 0; j < HR_HYPHEN_COUNT; j++) {
|
|
81
|
+
if (state.buffer.charCodeAt(i + j) !== HYPHEN)
|
|
82
|
+
return 'not_match';
|
|
83
|
+
}
|
|
84
|
+
i += HR_HYPHEN_COUNT;
|
|
85
|
+
// after hyphens, only line whitespace allowed before newline/EOF
|
|
86
|
+
while (i < state.buffer.length) {
|
|
87
|
+
const c = state.buffer.charCodeAt(i);
|
|
88
|
+
if (c === NEWLINE)
|
|
89
|
+
break;
|
|
90
|
+
if (!is_line_whitespace(c))
|
|
91
|
+
return 'not_match';
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
// if buffer ends without newline/EOF confirmation, need more input (unless forced)
|
|
95
|
+
if (i >= state.buffer.length && !forced) {
|
|
96
|
+
return 'need_more';
|
|
97
|
+
}
|
|
98
|
+
flush_text(state);
|
|
99
|
+
close_paragraph(state);
|
|
100
|
+
const id = alloc_id(state);
|
|
101
|
+
emit(state, {
|
|
102
|
+
type: 'void',
|
|
103
|
+
id,
|
|
104
|
+
node_type: 'Hr',
|
|
105
|
+
start: offset(state, start),
|
|
106
|
+
end: offset(state, i),
|
|
107
|
+
});
|
|
108
|
+
// `i` sits at the line's newline, or at the buffer end — in forced mode
|
|
109
|
+
// always the buffer end (a newline would have resolved this line during
|
|
110
|
+
// `feed`, so a forced HR is only ever the document's final line). Trailing
|
|
111
|
+
// newline and blank lines absorb at the process-loop top.
|
|
112
|
+
state.pos = i;
|
|
113
|
+
state.skip_blank_lines = true;
|
|
114
|
+
state.column = 0;
|
|
115
|
+
state.prev_char = NEWLINE;
|
|
116
|
+
return 'consumed';
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Try to open a code block at column 0. A complete opener line is decisive —
|
|
120
|
+
* raw mode is absolute, so fence-ness never depends on a closer arriving:
|
|
121
|
+
* an unclosed fence consumes to EOF as content. Any open paragraph closes
|
|
122
|
+
* first (fences interrupt paragraphs, matching the sync parser). The only
|
|
123
|
+
* hold is the opener line itself; an opener cut off at EOF without its
|
|
124
|
+
* newline stays text (the orchestrator treats `need_more` as text in forced
|
|
125
|
+
* mode).
|
|
126
|
+
*
|
|
127
|
+
* @nodocs
|
|
128
|
+
*/
|
|
129
|
+
export const try_codeblock_open = (state) => {
|
|
130
|
+
const start = state.pos;
|
|
131
|
+
let i = start;
|
|
132
|
+
// count backticks
|
|
133
|
+
let backtick_count = 0;
|
|
134
|
+
while (i < state.buffer.length && state.buffer.charCodeAt(i) === BACKTICK) {
|
|
135
|
+
backtick_count++;
|
|
136
|
+
i++;
|
|
137
|
+
}
|
|
138
|
+
if (backtick_count < MIN_CODEBLOCK_BACKTICKS) {
|
|
139
|
+
// if buffer ended mid-backtick-sequence, need more input
|
|
140
|
+
if (i >= state.buffer.length)
|
|
141
|
+
return 'need_more';
|
|
142
|
+
// definitely not enough backticks
|
|
143
|
+
return 'not_match';
|
|
144
|
+
}
|
|
145
|
+
// find end of opening fence line (need newline)
|
|
146
|
+
const lang_start = i;
|
|
147
|
+
while (i < state.buffer.length) {
|
|
148
|
+
const c = state.buffer.charCodeAt(i);
|
|
149
|
+
if (c === NEWLINE || is_line_whitespace(c))
|
|
150
|
+
break;
|
|
151
|
+
i++;
|
|
152
|
+
}
|
|
153
|
+
const lang = i > lang_start ? state.buffer.slice(lang_start, i) : null;
|
|
154
|
+
// skip trailing whitespace on opening fence line
|
|
155
|
+
while (i < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(i))) {
|
|
156
|
+
i++;
|
|
157
|
+
}
|
|
158
|
+
// must find newline
|
|
159
|
+
if (i >= state.buffer.length)
|
|
160
|
+
return 'need_more';
|
|
161
|
+
if (state.buffer.charCodeAt(i) !== NEWLINE)
|
|
162
|
+
return 'not_match';
|
|
163
|
+
i++; // consume newline
|
|
164
|
+
flush_text(state);
|
|
165
|
+
close_paragraph(state);
|
|
166
|
+
const id = alloc_id(state);
|
|
167
|
+
const cb_start = offset(state, start);
|
|
168
|
+
emit(state, { type: 'open', id, node_type: 'Codeblock', start: cb_start, lang });
|
|
169
|
+
state.codeblock = { id, backtick_count, text_id: null, fence_indent: 0, start: cb_start };
|
|
170
|
+
state.pos = i;
|
|
171
|
+
state.column = 0;
|
|
172
|
+
state.prev_char = NEWLINE;
|
|
173
|
+
return 'consumed';
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Emit codeblock content as `text` (first chunk) or `append_text` (subsequent
|
|
177
|
+
* chunks), updating `cb.text_id` on first emit. `content_start` is the local
|
|
178
|
+
* buffer position where the content slice began — only used to compute the
|
|
179
|
+
* global byte offset for the first `text` opcode.
|
|
180
|
+
*
|
|
181
|
+
* @nodocs
|
|
182
|
+
*/
|
|
183
|
+
export const emit_codeblock_text = (state, cb, content, content_start) => {
|
|
184
|
+
if (cb.text_id !== null) {
|
|
185
|
+
emit(state, { type: 'append_text', id: cb.text_id, content });
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
const id = alloc_id(state);
|
|
189
|
+
const text_start = offset(state, content_start);
|
|
190
|
+
emit(state, {
|
|
191
|
+
type: 'text',
|
|
192
|
+
id,
|
|
193
|
+
content,
|
|
194
|
+
text_type: 'Text',
|
|
195
|
+
start: text_start,
|
|
196
|
+
end: text_start + content.length,
|
|
197
|
+
});
|
|
198
|
+
cb.text_id = id;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Drain the remaining buffer as codeblock content at EOF, looking for a closing
|
|
203
|
+
* fence whose terminator is EOF rather than `\n`. Matches the sync lexer's
|
|
204
|
+
* `#match_fence`, which allows a fence at the end of the document.
|
|
205
|
+
*
|
|
206
|
+
* If a fence is found, the codeblock closes normally. Otherwise the remaining
|
|
207
|
+
* buffer becomes content and `close_codeblock_at_eof` (in
|
|
208
|
+
* `mdz_stream_parser_state.ts`) emits the plain close after process completes
|
|
209
|
+
* (an unclosed fence consumes to EOF — never reverted).
|
|
210
|
+
*
|
|
211
|
+
* @nodocs
|
|
212
|
+
*/
|
|
213
|
+
export const process_codeblock_forced = (state) => {
|
|
214
|
+
const cb = state.codeblock;
|
|
215
|
+
const start = state.pos;
|
|
216
|
+
while (state.pos < state.buffer.length) {
|
|
217
|
+
if (state.column === 0) {
|
|
218
|
+
const fence_match = match_codeblock_close(state, cb.backtick_count, true);
|
|
219
|
+
if (fence_match !== -1) {
|
|
220
|
+
// emit content up to the closing fence (excluding trailing newline before fence)
|
|
221
|
+
let content_end = state.pos;
|
|
222
|
+
if (content_end > start && state.buffer.charCodeAt(content_end - 1) === NEWLINE) {
|
|
223
|
+
content_end--;
|
|
224
|
+
}
|
|
225
|
+
const content = state.buffer.slice(start, content_end);
|
|
226
|
+
if (content.length > 0) {
|
|
227
|
+
emit_codeblock_text(state, cb, content, start);
|
|
228
|
+
}
|
|
229
|
+
// the closer here is always EOF-terminated (`fence_match` is the
|
|
230
|
+
// buffer end): a newline-terminated closer resolves during `feed`,
|
|
231
|
+
// whose hold retains at most a trailing newline plus a partial
|
|
232
|
+
// fence line. So — like the non-forced path's `fence_match - 1` —
|
|
233
|
+
// the close `end` excludes any line terminator
|
|
234
|
+
emit(state, { type: 'close', id: cb.id, end: offset(state, fence_match) });
|
|
235
|
+
state.pos = fence_match;
|
|
236
|
+
state.codeblock = null;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const c = state.buffer.charCodeAt(state.pos);
|
|
241
|
+
if (c === NEWLINE) {
|
|
242
|
+
state.column = 0;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
state.column++;
|
|
246
|
+
}
|
|
247
|
+
state.pos++;
|
|
248
|
+
}
|
|
249
|
+
// no closing fence — dump the rest as codeblock content; caller's
|
|
250
|
+
// `close_codeblock_at_eof` then closes the codeblock at EOF
|
|
251
|
+
const remaining = state.buffer.slice(start, state.pos);
|
|
252
|
+
if (remaining.length > 0) {
|
|
253
|
+
emit_codeblock_text(state, cb, remaining, start);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Process bytes while in codeblock mode.
|
|
258
|
+
* Returns true if processing should continue, false if more input needed.
|
|
259
|
+
*
|
|
260
|
+
* @nodocs
|
|
261
|
+
*/
|
|
262
|
+
export const process_codeblock = (state) => {
|
|
263
|
+
const cb = state.codeblock;
|
|
264
|
+
const start = state.pos;
|
|
265
|
+
while (state.pos < state.buffer.length) {
|
|
266
|
+
// check for closing fence at column 0
|
|
267
|
+
if (state.column === 0) {
|
|
268
|
+
const fence_match = match_codeblock_close(state, cb.backtick_count);
|
|
269
|
+
if (fence_match === -2) {
|
|
270
|
+
// need more input — hold from this position (don't consume potential fence chars)
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
if (fence_match !== -1) {
|
|
274
|
+
// emit content up to the closing fence (excluding trailing newline before fence)
|
|
275
|
+
let content_end = state.pos;
|
|
276
|
+
if (content_end > start && state.buffer.charCodeAt(content_end - 1) === NEWLINE) {
|
|
277
|
+
content_end--;
|
|
278
|
+
}
|
|
279
|
+
const content = state.buffer.slice(start, content_end);
|
|
280
|
+
if (content.length > 0) {
|
|
281
|
+
emit_codeblock_text(state, cb, content, start);
|
|
282
|
+
}
|
|
283
|
+
// codeblock end = position of newline after closing fence (exclusive)
|
|
284
|
+
emit(state, { type: 'close', id: cb.id, end: offset(state, fence_match - 1) });
|
|
285
|
+
state.pos = fence_match;
|
|
286
|
+
state.codeblock = null;
|
|
287
|
+
state.column = 0;
|
|
288
|
+
state.prev_char = NEWLINE;
|
|
289
|
+
// trailing newlines and blank lines absorb at the process-loop top
|
|
290
|
+
state.skip_blank_lines = true;
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// not a closing fence — advance
|
|
295
|
+
const c = state.buffer.charCodeAt(state.pos);
|
|
296
|
+
if (c === NEWLINE) {
|
|
297
|
+
state.column = 0;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
state.column++;
|
|
301
|
+
}
|
|
302
|
+
state.pos++;
|
|
303
|
+
}
|
|
304
|
+
// buffer exhausted or broke for potential fence — emit content so far, hold trailing data.
|
|
305
|
+
// Hold trailing \n (could be before a closing fence in next chunk), and also hold
|
|
306
|
+
// \n + backticks when we broke mid-fence-check (the \n precedes the backticks and
|
|
307
|
+
// would be stripped by fence close, so must not be emitted as content yet).
|
|
308
|
+
let emit_end = state.pos;
|
|
309
|
+
if (emit_end > start && state.buffer.charCodeAt(emit_end - 1) === NEWLINE) {
|
|
310
|
+
emit_end--;
|
|
311
|
+
state.pos = emit_end; // leave \n (+ any held backticks after it) in buffer
|
|
312
|
+
}
|
|
313
|
+
const content = state.buffer.slice(start, emit_end);
|
|
314
|
+
if (content.length > 0) {
|
|
315
|
+
emit_codeblock_text(state, cb, content, start);
|
|
316
|
+
}
|
|
317
|
+
return false; // need more input
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* Process buffered lines while inside an in-item code block
|
|
321
|
+
* (`fence_indent > 0`). Operates on complete lines only — a partial line at
|
|
322
|
+
* the buffer end holds until its newline arrives (line-bounded, like the
|
|
323
|
+
* opener hold), keeping the per-line indent strip and closer probe
|
|
324
|
+
* deterministic across chunk boundaries. Content lines strip
|
|
325
|
+
* `min(fence_indent, line_indent)` and emit joined by `'\n'` separators
|
|
326
|
+
* (first line bare, later lines prefixed), so emitted content never carries
|
|
327
|
+
* the region's final newline — `close_codeblock_at_eof` therefore skips its
|
|
328
|
+
* trailing-newline trim for in-item fences.
|
|
329
|
+
*
|
|
330
|
+
* A closing fence — any indent <= the opener's, a run of >= the opener's
|
|
331
|
+
* backticks, only whitespace to the line end — closes the block with `pos`
|
|
332
|
+
* left at its terminating newline, so the list line classifier dispatches
|
|
333
|
+
* what follows (mirroring the sync lexer's loop shape). In forced mode EOF
|
|
334
|
+
* terminates closer and content lines.
|
|
335
|
+
*
|
|
336
|
+
* Returns `false` when more input is needed; never in forced mode.
|
|
337
|
+
*
|
|
338
|
+
* @nodocs
|
|
339
|
+
*/
|
|
340
|
+
export const process_codeblock_in_item = (state, forced) => {
|
|
341
|
+
const cb = state.codeblock;
|
|
342
|
+
while (state.pos < state.buffer.length) {
|
|
343
|
+
const line_start = state.pos;
|
|
344
|
+
let j = line_start;
|
|
345
|
+
while (j < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(j))) {
|
|
346
|
+
j++;
|
|
347
|
+
}
|
|
348
|
+
if (j >= state.buffer.length && !forced)
|
|
349
|
+
return false; // partial line — hold
|
|
350
|
+
const line_indent = j - line_start;
|
|
351
|
+
// closer probe
|
|
352
|
+
if (line_indent <= cb.fence_indent &&
|
|
353
|
+
j < state.buffer.length &&
|
|
354
|
+
state.buffer.charCodeAt(j) === BACKTICK) {
|
|
355
|
+
let k = j;
|
|
356
|
+
let count = 0;
|
|
357
|
+
while (k < state.buffer.length && state.buffer.charCodeAt(k) === BACKTICK) {
|
|
358
|
+
count++;
|
|
359
|
+
k++;
|
|
360
|
+
}
|
|
361
|
+
if (k >= state.buffer.length && !forced)
|
|
362
|
+
return false; // run could still grow
|
|
363
|
+
if (count >= cb.backtick_count) {
|
|
364
|
+
let m = k;
|
|
365
|
+
while (m < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(m))) {
|
|
366
|
+
m++;
|
|
367
|
+
}
|
|
368
|
+
if (m >= state.buffer.length && !forced)
|
|
369
|
+
return false; // trailing content could disqualify
|
|
370
|
+
if (m >= state.buffer.length || state.buffer.charCodeAt(m) === NEWLINE) {
|
|
371
|
+
emit(state, { type: 'close', id: cb.id, end: offset(state, m) });
|
|
372
|
+
state.codeblock = null;
|
|
373
|
+
state.pos = m; // at the closer's newline (or EOF)
|
|
374
|
+
state.column = m - line_start;
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// not a closer — the line is content
|
|
379
|
+
}
|
|
380
|
+
// content line — emit it whole once its newline arrives (or at EOF when forced)
|
|
381
|
+
const nl = state.buffer.indexOf('\n', j);
|
|
382
|
+
if (nl === -1 && !forced)
|
|
383
|
+
return false; // hold the partial line
|
|
384
|
+
const line_end = nl === -1 ? state.buffer.length : nl;
|
|
385
|
+
const strip = line_start + Math.min(cb.fence_indent, line_indent);
|
|
386
|
+
const content = state.buffer.slice(strip, line_end);
|
|
387
|
+
emit_codeblock_text(state, cb, cb.text_id !== null ? '\n' + content : content, strip);
|
|
388
|
+
state.pos = nl === -1 ? line_end : line_end + 1;
|
|
389
|
+
state.column = 0;
|
|
390
|
+
state.prev_char = NEWLINE;
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* Check if the buffer at current position has a codeblock closing fence —
|
|
396
|
+
* a run of at least `backtick_count` backticks with only whitespace to end
|
|
397
|
+
* of line. Returns the position after the fence (including trailing newline)
|
|
398
|
+
* on success, `-1` when definitely not a fence, or `-2` when more input is
|
|
399
|
+
* needed to decide.
|
|
400
|
+
*
|
|
401
|
+
* When `forced` is true, EOF after the fence counts as a valid terminator
|
|
402
|
+
* (matches `mdz_parse`'s handling of code blocks at end of input).
|
|
403
|
+
*
|
|
404
|
+
* @nodocs
|
|
405
|
+
*/
|
|
406
|
+
export const match_codeblock_close = (state, backtick_count, forced = false) => {
|
|
407
|
+
let i = state.pos;
|
|
408
|
+
// count backticks
|
|
409
|
+
let count = 0;
|
|
410
|
+
while (i < state.buffer.length && state.buffer.charCodeAt(i) === BACKTICK) {
|
|
411
|
+
count++;
|
|
412
|
+
i++;
|
|
413
|
+
}
|
|
414
|
+
if (count < backtick_count) {
|
|
415
|
+
// buffer ended mid-backtick-sequence — could still grow into a fence
|
|
416
|
+
if (!forced && i >= state.buffer.length)
|
|
417
|
+
return -2;
|
|
418
|
+
return -1;
|
|
419
|
+
}
|
|
420
|
+
// trailing whitespace allowed
|
|
421
|
+
while (i < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(i))) {
|
|
422
|
+
i++;
|
|
423
|
+
}
|
|
424
|
+
// must be followed by newline or EOF
|
|
425
|
+
if (i < state.buffer.length && state.buffer.charCodeAt(i) === NEWLINE) {
|
|
426
|
+
return i + 1;
|
|
427
|
+
}
|
|
428
|
+
if (i >= state.buffer.length) {
|
|
429
|
+
// in forced mode, EOF terminates the fence
|
|
430
|
+
if (forced)
|
|
431
|
+
return i;
|
|
432
|
+
// enough backticks but buffer ended — the run could still grow, or
|
|
433
|
+
// whitespace/newline/disqualifying content could follow
|
|
434
|
+
return -2;
|
|
435
|
+
}
|
|
436
|
+
return -1;
|
|
437
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline-formatting handlers (bold, italic, strikethrough, inline code)
|
|
3
|
+
* for the streaming mdz parser.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { type MdzStreamParserState } from './mdz_stream_parser_state.js';
|
|
8
|
+
/**
|
|
9
|
+
* Try to open a doubled-delimiter inline (`**` bold, `~~` strikethrough) —
|
|
10
|
+
* the streaming analogue of the lexer's `#tokenize_double_delimiter`. No
|
|
11
|
+
* word-boundary checks, matching the one-shot parse: doubled delimiters are
|
|
12
|
+
* unambiguous enough to pair anywhere, and the first closer wins.
|
|
13
|
+
*
|
|
14
|
+
* @nodocs
|
|
15
|
+
*/
|
|
16
|
+
export declare const try_double_delimiter: (state: MdzStreamParserState, delimiter: "**" | "~~", node_type: "Bold" | "Strikethrough", forced?: boolean) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Try to open an italic span (`_`) — the streaming analogue of the lexer's
|
|
19
|
+
* `#tokenize_italic`.
|
|
20
|
+
*
|
|
21
|
+
* @nodocs
|
|
22
|
+
*/
|
|
23
|
+
export declare const try_italic: (state: MdzStreamParserState, forced?: boolean) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the char after a closing italic candidate permits the close (only
|
|
26
|
+
* `_` has close-boundary rules — `**`/`~~` close anywhere). Word chars and
|
|
27
|
+
* `_` itself (self-opaque — `_x__` never closes on the first `_` of a run)
|
|
28
|
+
* fail the boundary.
|
|
29
|
+
*
|
|
30
|
+
* At end of buffer the boundary is treated as valid — only reachable in
|
|
31
|
+
* forced mode, where end-of-input is a true boundary; while streaming,
|
|
32
|
+
* `process_inline` holds the closer decision until the next char arrives.
|
|
33
|
+
*
|
|
34
|
+
* @nodocs
|
|
35
|
+
*/
|
|
36
|
+
export declare const check_close_word_boundary: (state: MdzStreamParserState) => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Close an open delimiter-paired container (`Bold`, `Italic`, `Strikethrough`)
|
|
39
|
+
* at the given stack index, consuming the closing delimiter — read from the
|
|
40
|
+
* entry, so `**`/`~~` advance two chars and `_` one. The caller in
|
|
41
|
+
* `process_inline` already located the open via `find_open`, so we trust the
|
|
42
|
+
* index rather than re-walking the stack.
|
|
43
|
+
*
|
|
44
|
+
* @nodocs
|
|
45
|
+
*/
|
|
46
|
+
export declare const close_delimiter: (state: MdzStreamParserState, stack_idx: number) => boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Scan for the first potential closing `_` and check its word boundary (only
|
|
49
|
+
* italic has closer-boundary rules). Returns a tri-state:
|
|
50
|
+
* - `'confirmed'`: closer found in buffer with valid word boundary
|
|
51
|
+
* - `'rejected'`: closer found but with invalid word boundary (greedy first-match fails)
|
|
52
|
+
* - `'pending'`: no closer in buffer yet, or can't determine boundary
|
|
53
|
+
*
|
|
54
|
+
* The boundary check is self-opaque: a candidate followed by `_` itself is
|
|
55
|
+
* `'rejected'`, so underscore runs (`__init__`) degrade to literal text.
|
|
56
|
+
* While streaming, `'pending'` opens optimistically (the revert machinery
|
|
57
|
+
* corrects later); in forced mode it degrades to text.
|
|
58
|
+
*
|
|
59
|
+
* The delimiter search uses raw `indexOf` and stays amortized-linear without
|
|
60
|
+
* a memo: candidates are occurrences of the searched character itself, so a
|
|
61
|
+
* scan ends either at the next candidate (consecutive scans cover disjoint
|
|
62
|
+
* ranges) or at the end of the buffer with no candidates left to rescan it.
|
|
63
|
+
* The run-boundary probe (paragraph breaks, and list structure inside open
|
|
64
|
+
* lists) is bounded to the same window — only a boundary *before* the found
|
|
65
|
+
* delimiter matters — inheriting that amortization; an unbounded probe would
|
|
66
|
+
* rescan to EOF per candidate on single-paragraph input, going quadratic.
|
|
67
|
+
*
|
|
68
|
+
* @nodocs
|
|
69
|
+
*/
|
|
70
|
+
export declare const scan_closer_boundary: (state: MdzStreamParserState, forced?: boolean) => "confirmed" | "rejected" | "pending";
|
|
71
|
+
/** @nodocs */
|
|
72
|
+
export declare const try_code: (state: MdzStreamParserState, forced?: boolean) => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Compute search limit for inline code scanning.
|
|
75
|
+
* Finds the closest closing delimiter for any open formatting on the stack.
|
|
76
|
+
* This prevents backtick scanning from crossing bold/italic/etc boundaries.
|
|
77
|
+
*
|
|
78
|
+
* @nodocs
|
|
79
|
+
*/
|
|
80
|
+
export declare const code_search_limit: (state: MdzStreamParserState, from: number) => number;
|
|
81
|
+
/**
|
|
82
|
+
* Check if any inline formatting container (Bold, Italic, Strikethrough, Link)
|
|
83
|
+
* is on the stack above the current block boundary.
|
|
84
|
+
* Used to decide whether backtick can go optimistic — when formatting is open,
|
|
85
|
+
* the code span might cross the formatting boundary, so we hold instead.
|
|
86
|
+
*
|
|
87
|
+
* @nodocs
|
|
88
|
+
*/
|
|
89
|
+
export declare const has_inline_formatting_on_stack: (state: MdzStreamParserState) => boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Process content inside an optimistic inline Code container.
|
|
92
|
+
* Scans raw text (no inline formatting) until closing backtick or newline.
|
|
93
|
+
* Backtick closes (or reverts if empty). Newline reverts (code can't span lines).
|
|
94
|
+
*
|
|
95
|
+
* @nodocs
|
|
96
|
+
*/
|
|
97
|
+
export declare const process_code_content: (state: MdzStreamParserState) => void;
|
|
98
|
+
//# sourceMappingURL=mdz_stream_parser_inline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdz_stream_parser_inline.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_inline.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,EACN,KAAK,oBAAoB,EAYzB,MAAM,8BAA8B,CAAC;AA6GtC;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAChC,OAAO,oBAAoB,EAC3B,WAAW,IAAI,GAAG,IAAI,EACtB,WAAW,MAAM,GAAG,eAAe,EACnC,gBAAc,KACZ,OA2BF,CAAC;AAIF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,oBAAoB,EAAE,gBAAc,KAAG,OAyCxE,CAAC;AAIF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,oBAAoB,KAAG,OAOvE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,oBAAoB,EAAE,WAAW,MAAM,KAAG,OAuBhF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,oBAAoB,GAChC,OAAO,oBAAoB,EAC3B,gBAAc,KACZ,WAAW,GAAG,UAAU,GAAG,SAmB7B,CAAC;AAIF,cAAc;AACd,eAAO,MAAM,QAAQ,GAAI,OAAO,oBAAoB,EAAE,gBAAc,KAAG,OAoFtE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,oBAAoB,EAAE,MAAM,MAAM,KAAG,MAyB7E,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAAI,OAAO,oBAAoB,KAAG,OAO5E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,oBAAoB,KAAG,IA2ClE,CAAC"}
|