@fuzdev/mdz 0.3.0 → 0.5.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 +10 -17
- package/README.md +6 -3
- package/dist/Mdz.svelte +8 -7
- package/dist/Mdz.svelte.d.ts.map +1 -1
- package/dist/MdzNodeView.svelte +74 -30
- package/dist/MdzNodeView.svelte.d.ts +1 -2
- package/dist/MdzNodeView.svelte.d.ts.map +1 -1
- package/dist/MdzPrecompiled.svelte +3 -3
- package/dist/MdzPrecompiled.svelte.d.ts.map +1 -1
- package/dist/MdzRoot.svelte +3 -3
- package/dist/MdzRoot.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 +79 -31
- package/dist/MdzStreamNodeView.svelte.d.ts +1 -2
- package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
- package/dist/context_helpers.js +1 -1
- package/dist/mdz.d.ts +12 -1
- package/dist/mdz.d.ts.map +1 -1
- package/dist/mdz.js +1 -1
- package/dist/mdz_contexts.d.ts +33 -6
- package/dist/mdz_contexts.d.ts.map +1 -1
- package/dist/mdz_debug_work.d.ts.map +1 -1
- package/dist/mdz_helpers.d.ts +31 -3
- package/dist/mdz_helpers.d.ts.map +1 -1
- package/dist/mdz_helpers.js +104 -12
- package/dist/mdz_lexer.d.ts +3 -1
- package/dist/mdz_lexer.d.ts.map +1 -1
- package/dist/mdz_lexer.js +115 -31
- package/dist/mdz_opcodes.d.ts +3 -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 +25 -17
- package/dist/mdz_stream_parser.d.ts.map +1 -1
- package/dist/mdz_stream_parser.js +8 -8
- package/dist/mdz_stream_parser_block.js +5 -5
- package/dist/mdz_stream_parser_inline.js +4 -4
- package/dist/mdz_stream_parser_link.d.ts.map +1 -1
- package/dist/mdz_stream_parser_link.js +83 -11
- package/dist/mdz_stream_parser_list.d.ts.map +1 -1
- package/dist/mdz_stream_parser_list.js +13 -7
- package/dist/mdz_stream_parser_state.d.ts.map +1 -1
- package/dist/mdz_stream_parser_state.js +7 -7
- package/dist/mdz_stream_parser_table.d.ts.map +1 -1
- package/dist/mdz_stream_parser_table.js +16 -9
- package/dist/mdz_stream_parser_text.js +2 -2
- package/dist/mdz_stream_parser_url.js +7 -7
- package/dist/mdz_stream_state.svelte.d.ts +3 -1
- package/dist/mdz_stream_state.svelte.d.ts.map +1 -1
- package/dist/mdz_stream_state.svelte.js +4 -0
- package/dist/mdz_to_svelte.d.ts +17 -4
- package/dist/mdz_to_svelte.d.ts.map +1 -1
- package/dist/mdz_to_svelte.js +22 -9
- package/dist/mdz_token_parser.d.ts.map +1 -1
- package/dist/mdz_token_parser.js +25 -11
- package/dist/svelte_preprocess_mdz.d.ts +6 -3
- package/dist/svelte_preprocess_mdz.d.ts.map +1 -1
- package/dist/svelte_preprocess_mdz.js +11 -11
- package/package.json +25 -40
- package/src/lib/context_helpers.ts +2 -2
- package/src/lib/mdz.ts +15 -3
- package/src/lib/mdz_contexts.ts +41 -11
- package/src/lib/mdz_debug_work.ts +1 -1
- package/src/lib/mdz_helpers.ts +164 -43
- package/src/lib/mdz_lexer.ts +173 -80
- package/src/lib/mdz_opcodes.ts +3 -1
- package/src/lib/mdz_opcodes_to_nodes.ts +40 -30
- package/src/lib/mdz_stream_parser.ts +17 -17
- package/src/lib/mdz_stream_parser_block.ts +14 -14
- package/src/lib/mdz_stream_parser_inline.ts +18 -18
- package/src/lib/mdz_stream_parser_link.ts +88 -18
- package/src/lib/mdz_stream_parser_list.ts +31 -25
- package/src/lib/mdz_stream_parser_state.ts +27 -27
- package/src/lib/mdz_stream_parser_table.ts +38 -31
- package/src/lib/mdz_stream_parser_text.ts +3 -3
- package/src/lib/mdz_stream_parser_url.ts +11 -11
- package/src/lib/mdz_stream_state.svelte.ts +11 -8
- package/src/lib/mdz_to_svelte.ts +71 -27
- package/src/lib/mdz_token_parser.ts +52 -38
- package/src/lib/svelte_preprocess_mdz.ts +66 -60
- package/src/lib/tsdoc_mdz.ts +1 -1
package/src/lib/mdz_lexer.ts
CHANGED
|
@@ -26,6 +26,9 @@ import {
|
|
|
26
26
|
LEFT_ANGLE,
|
|
27
27
|
RIGHT_ANGLE,
|
|
28
28
|
SLASH,
|
|
29
|
+
EQUALS,
|
|
30
|
+
APOSTROPHE,
|
|
31
|
+
DOUBLE_QUOTE,
|
|
29
32
|
PERIOD,
|
|
30
33
|
LEFT_BRACKET,
|
|
31
34
|
LEFT_PAREN,
|
|
@@ -55,11 +58,11 @@ import {
|
|
|
55
58
|
TEXT_CLASS_NEWLINE,
|
|
56
59
|
TEXT_CLASS_STOP,
|
|
57
60
|
TEXT_CLASS_URL,
|
|
58
|
-
TEXT_SCAN_CLASS
|
|
61
|
+
TEXT_SCAN_CLASS
|
|
59
62
|
} from './mdz_helpers.ts';
|
|
60
63
|
|
|
61
|
-
import type {MdzTableAlign} from './mdz.ts';
|
|
62
|
-
import {mdz_debug_work} from './mdz_debug_work.ts';
|
|
64
|
+
import type { MdzAttribute, MdzTableAlign } from './mdz.ts';
|
|
65
|
+
import { mdz_debug_work } from './mdz_debug_work.ts';
|
|
63
66
|
|
|
64
67
|
//
|
|
65
68
|
// Token types
|
|
@@ -177,12 +180,14 @@ export interface MdzTokenTagOpen extends MdzTokenBase {
|
|
|
177
180
|
type: 'tag_open';
|
|
178
181
|
name: string;
|
|
179
182
|
is_component: boolean;
|
|
183
|
+
attributes: Array<MdzAttribute>;
|
|
180
184
|
}
|
|
181
185
|
|
|
182
186
|
export interface MdzTokenTagSelfClose extends MdzTokenBase {
|
|
183
187
|
type: 'tag_self_close';
|
|
184
188
|
name: string;
|
|
185
189
|
is_component: boolean;
|
|
190
|
+
attributes: Array<MdzAttribute>;
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
export interface MdzTokenTagClose extends MdzTokenBase {
|
|
@@ -295,8 +300,8 @@ export class MdzLexer {
|
|
|
295
300
|
#index: number = 0;
|
|
296
301
|
#tokens: Array<MdzToken> = [];
|
|
297
302
|
#max_search_index: number = Number.MAX_SAFE_INTEGER;
|
|
298
|
-
#search_memo: Map<string, {from: number; result: number}> = new Map();
|
|
299
|
-
#break_memo: {from: number; result: number} | null = null;
|
|
303
|
+
#search_memo: Map<string, { from: number; result: number }> = new Map();
|
|
304
|
+
#break_memo: { from: number; result: number } | null = null;
|
|
300
305
|
/**
|
|
301
306
|
* Whether `#tokenize_text` is inside a list-item inline run: single
|
|
302
307
|
* newlines are soft breaks whose following structural indent strips from
|
|
@@ -394,7 +399,7 @@ export class MdzLexer {
|
|
|
394
399
|
mdz_debug_work.total += (result === -1 ? this.#text.length : result) - from;
|
|
395
400
|
}
|
|
396
401
|
if (memo === undefined) {
|
|
397
|
-
this.#search_memo.set(needle, {from, result});
|
|
402
|
+
this.#search_memo.set(needle, { from, result });
|
|
398
403
|
} else {
|
|
399
404
|
memo.from = from;
|
|
400
405
|
memo.result = result;
|
|
@@ -421,7 +426,7 @@ export class MdzLexer {
|
|
|
421
426
|
if (this.#is_at_paragraph_break()) {
|
|
422
427
|
const start = this.#index;
|
|
423
428
|
this.#skip_blank_lines();
|
|
424
|
-
this.#tokens.push({type: 'paragraph_break', start, end: this.#index});
|
|
429
|
+
this.#tokens.push({ type: 'paragraph_break', start, end: this.#index });
|
|
425
430
|
continue;
|
|
426
431
|
}
|
|
427
432
|
|
|
@@ -540,7 +545,7 @@ export class MdzLexer {
|
|
|
540
545
|
type: 'heading_start',
|
|
541
546
|
level: hash_count as 1 | 2 | 3 | 4 | 5 | 6,
|
|
542
547
|
start,
|
|
543
|
-
end: this.#index
|
|
548
|
+
end: this.#index // end of "## " prefix
|
|
544
549
|
});
|
|
545
550
|
|
|
546
551
|
// Find end-of-line to bound nested tokenizers (prevents tag scanner from scanning past heading)
|
|
@@ -580,7 +585,7 @@ export class MdzLexer {
|
|
|
580
585
|
this.#max_search_index = saved_max;
|
|
581
586
|
|
|
582
587
|
// Emit heading_end marker so the token parser knows where heading content stops
|
|
583
|
-
this.#tokens.push({type: 'heading_end', start: this.#index, end: this.#index});
|
|
588
|
+
this.#tokens.push({ type: 'heading_end', start: this.#index, end: this.#index });
|
|
584
589
|
|
|
585
590
|
// Skip newlines after heading
|
|
586
591
|
this.#skip_blank_lines();
|
|
@@ -613,7 +618,7 @@ export class MdzLexer {
|
|
|
613
618
|
const start = this.#index;
|
|
614
619
|
this.#index = i; // consume up to but not including newline
|
|
615
620
|
|
|
616
|
-
this.#tokens.push({type: 'hr', start, end: this.#index});
|
|
621
|
+
this.#tokens.push({ type: 'hr', start, end: this.#index });
|
|
617
622
|
|
|
618
623
|
// Skip newlines after HR
|
|
619
624
|
this.#skip_blank_lines();
|
|
@@ -641,7 +646,7 @@ export class MdzLexer {
|
|
|
641
646
|
* Match a quote prefix in the complete sync text — never `'pending'`.
|
|
642
647
|
* See `mdz_match_blockquote_prefix`.
|
|
643
648
|
*/
|
|
644
|
-
#match_blockquote_prefix(i: number): {depth: number; content_start: number} | null {
|
|
649
|
+
#match_blockquote_prefix(i: number): { depth: number; content_start: number } | null {
|
|
645
650
|
const prefix = mdz_match_blockquote_prefix(this.#text, i, true);
|
|
646
651
|
return prefix === 'pending' ? null : prefix;
|
|
647
652
|
}
|
|
@@ -694,7 +699,7 @@ export class MdzLexer {
|
|
|
694
699
|
// quote's content parses exactly like a standalone document (a complete
|
|
695
700
|
// fence opener line on the last quote line opens an empty fence, not text)
|
|
696
701
|
const parts: Array<string> = [];
|
|
697
|
-
const segments: Array<{inner_start: number; source_start: number}> = [];
|
|
702
|
+
const segments: Array<{ inner_start: number; source_start: number }> = [];
|
|
698
703
|
let inner_len = 0;
|
|
699
704
|
let line_first = first;
|
|
700
705
|
let last_line_end = first;
|
|
@@ -702,7 +707,7 @@ export class MdzLexer {
|
|
|
702
707
|
const strip = line_first + mdz_blockquote_strip_width(this.#text, line_first);
|
|
703
708
|
let nl = this.#text.indexOf('\n', line_first);
|
|
704
709
|
if (nl === -1) nl = this.#text.length;
|
|
705
|
-
segments.push({inner_start: inner_len, source_start: strip});
|
|
710
|
+
segments.push({ inner_start: inner_len, source_start: strip });
|
|
706
711
|
const content = this.#text.slice(Math.min(strip, nl), nl < this.#text.length ? nl + 1 : nl);
|
|
707
712
|
parts.push(content);
|
|
708
713
|
inner_len += content.length;
|
|
@@ -727,14 +732,14 @@ export class MdzLexer {
|
|
|
727
732
|
line_first = next_first;
|
|
728
733
|
}
|
|
729
734
|
|
|
730
|
-
this.#tokens.push({type: 'blockquote_open', start: first, end: first});
|
|
735
|
+
this.#tokens.push({ type: 'blockquote_open', start: first, end: first });
|
|
731
736
|
const inner_tokens = new MdzLexer(parts.join('')).tokenize();
|
|
732
737
|
for (const token of inner_tokens) {
|
|
733
738
|
token.start = mdz_remap_segments(segments, token.start);
|
|
734
739
|
token.end = mdz_remap_segments(segments, token.end);
|
|
735
740
|
this.#tokens.push(token);
|
|
736
741
|
}
|
|
737
|
-
this.#tokens.push({type: 'blockquote_close', start: last_line_end, end: last_line_end});
|
|
742
|
+
this.#tokens.push({ type: 'blockquote_close', start: last_line_end, end: last_line_end });
|
|
738
743
|
this.#index = last_line_end;
|
|
739
744
|
}
|
|
740
745
|
|
|
@@ -763,7 +768,7 @@ export class MdzLexer {
|
|
|
763
768
|
if (align === null || align.length !== header_cells.length) return false;
|
|
764
769
|
|
|
765
770
|
// committed — emit the table; the delimiter row is structural (not emitted)
|
|
766
|
-
this.#tokens.push({type: 'table_open', align, start: table_start, end: header_end});
|
|
771
|
+
this.#tokens.push({ type: 'table_open', align, start: table_start, end: header_end });
|
|
767
772
|
this.#emit_table_row(header_cells, true, table_start, header_end);
|
|
768
773
|
|
|
769
774
|
this.#index = delim_end;
|
|
@@ -781,23 +786,23 @@ export class MdzLexer {
|
|
|
781
786
|
table_end = row_end;
|
|
782
787
|
}
|
|
783
788
|
|
|
784
|
-
this.#tokens.push({type: 'table_close', start: table_end, end: table_end});
|
|
789
|
+
this.#tokens.push({ type: 'table_close', start: table_end, end: table_end });
|
|
785
790
|
this.#skip_blank_lines();
|
|
786
791
|
return true;
|
|
787
792
|
}
|
|
788
793
|
|
|
789
794
|
/** Emit one table row's open / cell / close tokens. */
|
|
790
795
|
#emit_table_row(
|
|
791
|
-
cells: Array<{start: number; end: number}>,
|
|
796
|
+
cells: Array<{ start: number; end: number }>,
|
|
792
797
|
header: boolean,
|
|
793
798
|
row_start: number,
|
|
794
|
-
row_end: number
|
|
799
|
+
row_end: number
|
|
795
800
|
): void {
|
|
796
|
-
this.#tokens.push({type: 'table_row_open', header, start: row_start, end: row_start});
|
|
801
|
+
this.#tokens.push({ type: 'table_row_open', header, start: row_start, end: row_start });
|
|
797
802
|
for (const cell of cells) {
|
|
798
803
|
this.#emit_table_cell(cell.start, cell.end);
|
|
799
804
|
}
|
|
800
|
-
this.#tokens.push({type: 'table_row_close', start: row_end, end: row_end});
|
|
805
|
+
this.#tokens.push({ type: 'table_row_close', start: row_end, end: row_end });
|
|
801
806
|
}
|
|
802
807
|
|
|
803
808
|
/**
|
|
@@ -807,9 +812,9 @@ export class MdzLexer {
|
|
|
807
812
|
* stops at that bound and unescapes `\|`.
|
|
808
813
|
*/
|
|
809
814
|
#emit_table_cell(cell_start: number, cell_end: number): void {
|
|
810
|
-
this.#tokens.push({type: 'table_cell_open', start: cell_start, end: cell_start});
|
|
815
|
+
this.#tokens.push({ type: 'table_cell_open', start: cell_start, end: cell_start });
|
|
811
816
|
this.#tokenize_cell_inline(cell_start, cell_end);
|
|
812
|
-
this.#tokens.push({type: 'table_cell_close', start: cell_end, end: cell_end});
|
|
817
|
+
this.#tokens.push({ type: 'table_cell_close', start: cell_end, end: cell_end });
|
|
813
818
|
}
|
|
814
819
|
|
|
815
820
|
/**
|
|
@@ -838,9 +843,9 @@ export class MdzLexer {
|
|
|
838
843
|
*/
|
|
839
844
|
#match_item_table_head(
|
|
840
845
|
first: number,
|
|
841
|
-
marker_indent: number
|
|
846
|
+
marker_indent: number
|
|
842
847
|
): {
|
|
843
|
-
header_cells: Array<{start: number; end: number}>;
|
|
848
|
+
header_cells: Array<{ start: number; end: number }>;
|
|
844
849
|
header_end: number;
|
|
845
850
|
align: Array<MdzTableAlign>;
|
|
846
851
|
delim_end: number;
|
|
@@ -857,7 +862,7 @@ export class MdzLexer {
|
|
|
857
862
|
if (delim_end === -1) delim_end = this.#text.length;
|
|
858
863
|
const align = mdz_parse_table_delimiter(this.#text, dj, delim_end);
|
|
859
864
|
if (align === null || align.length !== header_cells.length) return null;
|
|
860
|
-
return {header_cells, header_end, align, delim_end};
|
|
865
|
+
return { header_cells, header_end, align, delim_end };
|
|
861
866
|
}
|
|
862
867
|
|
|
863
868
|
/** Whether a table opens at `first` as a block child of an item at `marker_indent`. */
|
|
@@ -874,7 +879,12 @@ export class MdzLexer {
|
|
|
874
879
|
*/
|
|
875
880
|
#tokenize_item_table(first: number, marker_indent: number): void {
|
|
876
881
|
const head = this.#match_item_table_head(first, marker_indent)!;
|
|
877
|
-
this.#tokens.push({
|
|
882
|
+
this.#tokens.push({
|
|
883
|
+
type: 'table_open',
|
|
884
|
+
align: head.align,
|
|
885
|
+
start: first,
|
|
886
|
+
end: head.header_end
|
|
887
|
+
});
|
|
878
888
|
this.#emit_table_row(head.header_cells, true, first, head.header_end);
|
|
879
889
|
this.#index = head.delim_end;
|
|
880
890
|
let table_end = head.delim_end;
|
|
@@ -892,7 +902,7 @@ export class MdzLexer {
|
|
|
892
902
|
this.#index = row_end;
|
|
893
903
|
table_end = row_end;
|
|
894
904
|
}
|
|
895
|
-
this.#tokens.push({type: 'table_close', start: table_end, end: table_end});
|
|
905
|
+
this.#tokens.push({ type: 'table_close', start: table_end, end: table_end });
|
|
896
906
|
}
|
|
897
907
|
|
|
898
908
|
// -- List tokenizers --
|
|
@@ -964,7 +974,7 @@ export class MdzLexer {
|
|
|
964
974
|
}
|
|
965
975
|
if (levels[levels.length - 1]!.ordered === marker.ordered) {
|
|
966
976
|
// sibling item
|
|
967
|
-
this.#tokens.push({type: 'list_item_close', start: first, end: first});
|
|
977
|
+
this.#tokens.push({ type: 'list_item_close', start: first, end: first });
|
|
968
978
|
this.#list_item_open(marker);
|
|
969
979
|
} else {
|
|
970
980
|
// marker-type switch: close the list, open a new one at this indent
|
|
@@ -980,13 +990,13 @@ export class MdzLexer {
|
|
|
980
990
|
// empty item: only at an indent exactly matching an open level of
|
|
981
991
|
// the same marker type — anything else degrades below
|
|
982
992
|
const level_idx = levels.findIndex(
|
|
983
|
-
(l) => l.indent === indent && l.ordered === marker.ordered
|
|
993
|
+
(l) => l.indent === indent && l.ordered === marker.ordered
|
|
984
994
|
);
|
|
985
995
|
if (level_idx !== -1) {
|
|
986
996
|
while (levels.length - 1 > level_idx) {
|
|
987
997
|
this.#list_close(levels);
|
|
988
998
|
}
|
|
989
|
-
this.#tokens.push({type: 'list_item_close', start: first, end: first});
|
|
999
|
+
this.#tokens.push({ type: 'list_item_close', start: first, end: first });
|
|
990
1000
|
this.#list_item_open(marker);
|
|
991
1001
|
// consume the marker and any trailing whitespace, leaving #index
|
|
992
1002
|
// at the line's newline (or EOF)
|
|
@@ -1056,7 +1066,7 @@ export class MdzLexer {
|
|
|
1056
1066
|
while (levels.length - 1 > target_idx) {
|
|
1057
1067
|
this.#list_close(levels);
|
|
1058
1068
|
}
|
|
1059
|
-
this.#tokens.push({type: 'paragraph_break', start: line_start, end: line_start});
|
|
1069
|
+
this.#tokens.push({ type: 'paragraph_break', start: line_start, end: line_start });
|
|
1060
1070
|
this.#list_run(levels, first);
|
|
1061
1071
|
continue;
|
|
1062
1072
|
}
|
|
@@ -1091,13 +1101,13 @@ export class MdzLexer {
|
|
|
1091
1101
|
start: i,
|
|
1092
1102
|
marker_end: after,
|
|
1093
1103
|
content_start: after,
|
|
1094
|
-
empty: true
|
|
1104
|
+
empty: true
|
|
1095
1105
|
};
|
|
1096
1106
|
}
|
|
1097
1107
|
if (this.#text.charCodeAt(after) !== SPACE) return null;
|
|
1098
1108
|
const content_start = after + 1;
|
|
1099
1109
|
const empty = this.#rest_is_blank(content_start);
|
|
1100
|
-
return {ordered: false, number: 0, start: i, marker_end: after, content_start, empty};
|
|
1110
|
+
return { ordered: false, number: 0, start: i, marker_end: after, content_start, empty };
|
|
1101
1111
|
}
|
|
1102
1112
|
if (c >= ZERO && c <= NINE) {
|
|
1103
1113
|
let j = i + 1;
|
|
@@ -1119,13 +1129,13 @@ export class MdzLexer {
|
|
|
1119
1129
|
start: i,
|
|
1120
1130
|
marker_end: after,
|
|
1121
1131
|
content_start: after,
|
|
1122
|
-
empty: true
|
|
1132
|
+
empty: true
|
|
1123
1133
|
};
|
|
1124
1134
|
}
|
|
1125
1135
|
if (this.#text.charCodeAt(after) !== SPACE) return null;
|
|
1126
1136
|
const content_start = after + 1;
|
|
1127
1137
|
const empty = this.#rest_is_blank(content_start);
|
|
1128
|
-
return {ordered: true, number, start: i, marker_end: after, content_start, empty};
|
|
1138
|
+
return { ordered: true, number, start: i, marker_end: after, content_start, empty };
|
|
1129
1139
|
}
|
|
1130
1140
|
return null;
|
|
1131
1141
|
}
|
|
@@ -1162,26 +1172,31 @@ export class MdzLexer {
|
|
|
1162
1172
|
type: 'list_open',
|
|
1163
1173
|
ordered: marker.ordered,
|
|
1164
1174
|
start: marker.start,
|
|
1165
|
-
end: marker.start
|
|
1175
|
+
end: marker.start
|
|
1166
1176
|
});
|
|
1167
1177
|
this.#list_item_open(marker);
|
|
1168
|
-
levels.push({indent, ordered: marker.ordered});
|
|
1178
|
+
levels.push({ indent, ordered: marker.ordered });
|
|
1169
1179
|
}
|
|
1170
1180
|
|
|
1171
1181
|
/** Emit a `list_item_open` token for `marker`. */
|
|
1172
1182
|
#list_item_open(marker: MdzListMarker): void {
|
|
1173
1183
|
const end = marker.empty ? marker.marker_end : marker.content_start;
|
|
1174
1184
|
if (marker.ordered) {
|
|
1175
|
-
this.#tokens.push({
|
|
1185
|
+
this.#tokens.push({
|
|
1186
|
+
type: 'list_item_open',
|
|
1187
|
+
number: marker.number,
|
|
1188
|
+
start: marker.start,
|
|
1189
|
+
end
|
|
1190
|
+
});
|
|
1176
1191
|
} else {
|
|
1177
|
-
this.#tokens.push({type: 'list_item_open', start: marker.start, end});
|
|
1192
|
+
this.#tokens.push({ type: 'list_item_open', start: marker.start, end });
|
|
1178
1193
|
}
|
|
1179
1194
|
}
|
|
1180
1195
|
|
|
1181
1196
|
/** Close the deepest open level: its current item, then its list. */
|
|
1182
1197
|
#list_close(levels: Array<MdzListLevel>): void {
|
|
1183
|
-
this.#tokens.push({type: 'list_item_close', start: this.#index, end: this.#index});
|
|
1184
|
-
this.#tokens.push({type: 'list_close', start: this.#index, end: this.#index});
|
|
1198
|
+
this.#tokens.push({ type: 'list_item_close', start: this.#index, end: this.#index });
|
|
1199
|
+
this.#tokens.push({ type: 'list_close', start: this.#index, end: this.#index });
|
|
1185
1200
|
levels.pop();
|
|
1186
1201
|
}
|
|
1187
1202
|
|
|
@@ -1278,8 +1293,8 @@ export class MdzLexer {
|
|
|
1278
1293
|
*/
|
|
1279
1294
|
#match_fence(
|
|
1280
1295
|
first: number,
|
|
1281
|
-
fence_indent: number
|
|
1282
|
-
): {token: MdzTokenCodeblock; end: number} | null {
|
|
1296
|
+
fence_indent: number
|
|
1297
|
+
): { token: MdzTokenCodeblock; end: number } | null {
|
|
1283
1298
|
let i = first;
|
|
1284
1299
|
let backtick_count = 0;
|
|
1285
1300
|
while (i < this.#text.length && this.#text.charCodeAt(i) === BACKTICK) {
|
|
@@ -1336,8 +1351,8 @@ export class MdzLexer {
|
|
|
1336
1351
|
? this.#text.slice(content_start, Math.max(content_start, line_start - 1))
|
|
1337
1352
|
: content_lines.join('\n');
|
|
1338
1353
|
return {
|
|
1339
|
-
token: {type: 'codeblock', lang, content, start: first, end: m},
|
|
1340
|
-
end: m
|
|
1354
|
+
token: { type: 'codeblock', lang, content, start: first, end: m },
|
|
1355
|
+
end: m
|
|
1341
1356
|
};
|
|
1342
1357
|
}
|
|
1343
1358
|
}
|
|
@@ -1347,7 +1362,7 @@ export class MdzLexer {
|
|
|
1347
1362
|
if (fence_indent > 0) {
|
|
1348
1363
|
const line_end = nl === -1 ? this.#text.length : nl;
|
|
1349
1364
|
content_lines.push(
|
|
1350
|
-
this.#text.slice(line_start + Math.min(fence_indent, line_indent), line_end)
|
|
1365
|
+
this.#text.slice(line_start + Math.min(fence_indent, line_indent), line_end)
|
|
1351
1366
|
);
|
|
1352
1367
|
}
|
|
1353
1368
|
if (nl === -1) break;
|
|
@@ -1358,8 +1373,8 @@ export class MdzLexer {
|
|
|
1358
1373
|
let content = fence_indent === 0 ? this.#text.slice(content_start) : content_lines.join('\n');
|
|
1359
1374
|
if (content.endsWith('\n')) content = content.slice(0, -1);
|
|
1360
1375
|
return {
|
|
1361
|
-
token: {type: 'codeblock', lang, content, start: first, end: this.#text.length},
|
|
1362
|
-
end: this.#text.length
|
|
1376
|
+
token: { type: 'codeblock', lang, content, start: first, end: this.#text.length },
|
|
1377
|
+
end: this.#text.length
|
|
1363
1378
|
};
|
|
1364
1379
|
}
|
|
1365
1380
|
|
|
@@ -1453,7 +1468,7 @@ export class MdzLexer {
|
|
|
1453
1468
|
}
|
|
1454
1469
|
|
|
1455
1470
|
this.#index = content_end + 1;
|
|
1456
|
-
this.#tokens.push({type: 'code', content, start, end: this.#index});
|
|
1471
|
+
this.#tokens.push({ type: 'code', content, start, end: this.#index });
|
|
1457
1472
|
}
|
|
1458
1473
|
|
|
1459
1474
|
#tokenize_double_delimiter(delimiter: '**' | '~~', kind: 'bold' | 'strikethrough'): void {
|
|
@@ -1492,7 +1507,7 @@ export class MdzLexer {
|
|
|
1492
1507
|
const open_type = kind === 'bold' ? 'bold_open' : 'strikethrough_open';
|
|
1493
1508
|
const close_type = kind === 'bold' ? 'bold_close' : 'strikethrough_close';
|
|
1494
1509
|
const open_token_index = this.#tokens.length;
|
|
1495
|
-
this.#tokens.push({type: open_type, start, end: this.#index});
|
|
1510
|
+
this.#tokens.push({ type: open_type, start, end: this.#index });
|
|
1496
1511
|
|
|
1497
1512
|
// Set search boundary for nested parsers
|
|
1498
1513
|
const saved_max = this.#max_search_index;
|
|
@@ -1519,11 +1534,11 @@ export class MdzLexer {
|
|
|
1519
1534
|
return;
|
|
1520
1535
|
}
|
|
1521
1536
|
|
|
1522
|
-
this.#tokens.push({type: close_type, start: close_index, end: close_index + 2});
|
|
1537
|
+
this.#tokens.push({ type: close_type, start: close_index, end: close_index + 2 });
|
|
1523
1538
|
this.#index = close_index + 2;
|
|
1524
1539
|
} else {
|
|
1525
1540
|
// Didn't reach closing - convert opening to text
|
|
1526
|
-
this.#tokens[open_token_index] = {type: 'text', content: delimiter, start, end: start + 2};
|
|
1541
|
+
this.#tokens[open_token_index] = { type: 'text', content: delimiter, start, end: start + 2 };
|
|
1527
1542
|
}
|
|
1528
1543
|
}
|
|
1529
1544
|
|
|
@@ -1566,7 +1581,7 @@ export class MdzLexer {
|
|
|
1566
1581
|
// Emit open token
|
|
1567
1582
|
this.#index++;
|
|
1568
1583
|
const open_token_index = this.#tokens.length;
|
|
1569
|
-
this.#tokens.push({type: 'italic_open', start, end: this.#index});
|
|
1584
|
+
this.#tokens.push({ type: 'italic_open', start, end: this.#index });
|
|
1570
1585
|
|
|
1571
1586
|
// Set search boundary for nested parsers
|
|
1572
1587
|
const saved_max = this.#max_search_index;
|
|
@@ -1596,7 +1611,7 @@ export class MdzLexer {
|
|
|
1596
1611
|
this.#tokens.push({
|
|
1597
1612
|
type: 'italic_close',
|
|
1598
1613
|
start: close_index,
|
|
1599
|
-
end: close_index + 1
|
|
1614
|
+
end: close_index + 1
|
|
1600
1615
|
});
|
|
1601
1616
|
this.#index = close_index + 1;
|
|
1602
1617
|
} else {
|
|
@@ -1605,7 +1620,7 @@ export class MdzLexer {
|
|
|
1605
1620
|
type: 'text',
|
|
1606
1621
|
content: '_',
|
|
1607
1622
|
start,
|
|
1608
|
-
end: start + 1
|
|
1623
|
+
end: start + 1
|
|
1609
1624
|
};
|
|
1610
1625
|
}
|
|
1611
1626
|
}
|
|
@@ -1632,7 +1647,7 @@ export class MdzLexer {
|
|
|
1632
1647
|
this.#index++; // consume [ (dispatch guarantees it)
|
|
1633
1648
|
|
|
1634
1649
|
// Emit link_text_open
|
|
1635
|
-
this.#tokens.push({type: 'link_text_open', start, end: this.#index});
|
|
1650
|
+
this.#tokens.push({ type: 'link_text_open', start, end: this.#index });
|
|
1636
1651
|
|
|
1637
1652
|
// Tokenize children until ] — only `]` delimits link text; a bare `)`
|
|
1638
1653
|
// is ordinary content (matching the streaming parser, which has no `)`
|
|
@@ -1657,7 +1672,7 @@ export class MdzLexer {
|
|
|
1657
1672
|
this.#tokens.push({
|
|
1658
1673
|
type: 'link_text_close',
|
|
1659
1674
|
start: bracket_close_start,
|
|
1660
|
-
end: this.#index
|
|
1675
|
+
end: this.#index
|
|
1661
1676
|
});
|
|
1662
1677
|
|
|
1663
1678
|
// Check for (
|
|
@@ -1707,7 +1722,7 @@ export class MdzLexer {
|
|
|
1707
1722
|
reference,
|
|
1708
1723
|
link_type,
|
|
1709
1724
|
start: bracket_close_start + 1, // after ]
|
|
1710
|
-
end: this.#index
|
|
1725
|
+
end: this.#index
|
|
1711
1726
|
});
|
|
1712
1727
|
}
|
|
1713
1728
|
|
|
@@ -1784,9 +1799,78 @@ export class MdzLexer {
|
|
|
1784
1799
|
const first_char_code = tag_name.charCodeAt(0);
|
|
1785
1800
|
const is_component = first_char_code >= A_UPPER && first_char_code <= Z_UPPER;
|
|
1786
1801
|
|
|
1787
|
-
//
|
|
1788
|
-
|
|
1789
|
-
|
|
1802
|
+
// Attribute loop: consume space-separated attributes until `/>` or `>`.
|
|
1803
|
+
// Registry-blind — every syntactically valid attribute is parsed here;
|
|
1804
|
+
// policy (allowlist filtering) happens at render/build time. The scan is
|
|
1805
|
+
// a single forward pass bounded by the line: any newline (or a missing
|
|
1806
|
+
// separator, bad value, or duplicate name) rejects the whole tag to
|
|
1807
|
+
// literal text via the shared `#reject_tag` bailout.
|
|
1808
|
+
const attributes: Array<MdzAttribute> = [];
|
|
1809
|
+
const seen = new Set<string>(); // O(1) duplicate-name detection
|
|
1810
|
+
for (;;) {
|
|
1811
|
+
// skip a run of literal spaces only (tabs/newlines are not SPACE, so
|
|
1812
|
+
// they fall through to the reject path — the single-line, space-only rule)
|
|
1813
|
+
let had_space = false;
|
|
1814
|
+
while (this.#index < this.#text.length && this.#text.charCodeAt(this.#index) === SPACE) {
|
|
1815
|
+
this.#index++;
|
|
1816
|
+
had_space = true;
|
|
1817
|
+
}
|
|
1818
|
+
const c = this.#index < this.#text.length ? this.#text.charCodeAt(this.#index) : -1;
|
|
1819
|
+
// `/>` or `>` ends the open tag — break to the existing self-close / commit
|
|
1820
|
+
if (c === SLASH || c === RIGHT_ANGLE) break;
|
|
1821
|
+
// otherwise an attribute name must follow, space-separated from what
|
|
1822
|
+
// precedes it (`<a x="1"y="2">` rejects — `y` abuts the closing quote)
|
|
1823
|
+
if (c === -1 || !had_space || !is_letter(c)) {
|
|
1824
|
+
this.#reject_tag(start, bound);
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
// collect the attribute name (same grammar as tag names)
|
|
1828
|
+
const attr_start = this.#index;
|
|
1829
|
+
this.#index++; // first char already passed is_letter
|
|
1830
|
+
while (
|
|
1831
|
+
this.#index < this.#text.length &&
|
|
1832
|
+
is_tag_name_char(this.#text.charCodeAt(this.#index))
|
|
1833
|
+
) {
|
|
1834
|
+
this.#index++;
|
|
1835
|
+
}
|
|
1836
|
+
const name = this.#text.slice(attr_start, this.#index);
|
|
1837
|
+
// duplicate names are ambiguous (Svelte compile-errors) → literal tag
|
|
1838
|
+
if (seen.has(name)) {
|
|
1839
|
+
this.#reject_tag(start, bound);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
seen.add(name);
|
|
1843
|
+
let value: string | true = true; // bare name is boolean `true`
|
|
1844
|
+
if (this.#index < this.#text.length && this.#text.charCodeAt(this.#index) === EQUALS) {
|
|
1845
|
+
this.#index++; // consume =
|
|
1846
|
+
// the value must be immediately quoted — no space after `=`, no
|
|
1847
|
+
// `{` brace literal, no unquoted value (this one check covers all)
|
|
1848
|
+
const quote = this.#index < this.#text.length ? this.#text.charCodeAt(this.#index) : -1;
|
|
1849
|
+
if (quote !== DOUBLE_QUOTE && quote !== APOSTROPHE) {
|
|
1850
|
+
this.#reject_tag(start, bound);
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
this.#index++; // consume opening quote
|
|
1854
|
+
const value_start = this.#index;
|
|
1855
|
+
// scan to the matching quote, bounded by newline / text end — NOT
|
|
1856
|
+
// by `#max_search_index` (the open-tag scan never is); a `>` inside
|
|
1857
|
+
// the value is content, consumed atomically before `>` is sought
|
|
1858
|
+
while (
|
|
1859
|
+
this.#index < this.#text.length &&
|
|
1860
|
+
this.#text.charCodeAt(this.#index) !== quote &&
|
|
1861
|
+
this.#text.charCodeAt(this.#index) !== NEWLINE
|
|
1862
|
+
) {
|
|
1863
|
+
this.#index++;
|
|
1864
|
+
}
|
|
1865
|
+
if (this.#index >= this.#text.length || this.#text.charCodeAt(this.#index) === NEWLINE) {
|
|
1866
|
+
// unterminated quote (or newline inside the value) → literal tag
|
|
1867
|
+
this.#reject_tag(start, bound);
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
value = this.#text.slice(value_start, this.#index);
|
|
1871
|
+
this.#index++; // consume closing quote
|
|
1872
|
+
}
|
|
1873
|
+
attributes.push({ name, value, start: attr_start, end: this.#index });
|
|
1790
1874
|
}
|
|
1791
1875
|
|
|
1792
1876
|
// Check for self-closing />
|
|
@@ -1800,17 +1884,16 @@ export class MdzLexer {
|
|
|
1800
1884
|
type: 'tag_self_close',
|
|
1801
1885
|
name: tag_name,
|
|
1802
1886
|
is_component,
|
|
1887
|
+
attributes,
|
|
1803
1888
|
start,
|
|
1804
|
-
end: this.#index
|
|
1889
|
+
end: this.#index
|
|
1805
1890
|
});
|
|
1806
1891
|
return;
|
|
1807
1892
|
}
|
|
1808
1893
|
|
|
1809
1894
|
// Check for >
|
|
1810
1895
|
if (this.#index >= this.#text.length || this.#text.charCodeAt(this.#index) !== RIGHT_ANGLE) {
|
|
1811
|
-
this.#
|
|
1812
|
-
this.#index = start + 1;
|
|
1813
|
-
this.#emit_text('<', start);
|
|
1896
|
+
this.#reject_tag(start, bound);
|
|
1814
1897
|
return;
|
|
1815
1898
|
}
|
|
1816
1899
|
this.#index++; // consume >
|
|
@@ -1827,21 +1910,24 @@ export class MdzLexer {
|
|
|
1827
1910
|
const search_limit = Math.min(this.#max_search_index, this.#text.length);
|
|
1828
1911
|
const closing_tag_pos = this.#index_of(closing_tag, this.#index);
|
|
1829
1912
|
if (closing_tag_pos === -1 || closing_tag_pos > search_limit) {
|
|
1830
|
-
this.#
|
|
1831
|
-
this.#index = start + 1;
|
|
1832
|
-
this.#emit_text('<', start);
|
|
1913
|
+
this.#reject_tag(start, bound);
|
|
1833
1914
|
return;
|
|
1834
1915
|
}
|
|
1835
1916
|
if (this.#has_paragraph_break_between(this.#index, closing_tag_pos)) {
|
|
1836
|
-
this.#
|
|
1837
|
-
this.#index = start + 1;
|
|
1838
|
-
this.#emit_text('<', start);
|
|
1917
|
+
this.#reject_tag(start, bound);
|
|
1839
1918
|
return;
|
|
1840
1919
|
}
|
|
1841
1920
|
|
|
1842
1921
|
// Emit tag_open
|
|
1843
1922
|
const open_token_index = this.#tokens.length;
|
|
1844
|
-
this.#tokens.push({
|
|
1923
|
+
this.#tokens.push({
|
|
1924
|
+
type: 'tag_open',
|
|
1925
|
+
name: tag_name,
|
|
1926
|
+
is_component,
|
|
1927
|
+
attributes,
|
|
1928
|
+
start,
|
|
1929
|
+
end: this.#index
|
|
1930
|
+
});
|
|
1845
1931
|
|
|
1846
1932
|
// Tokenize children until the closing tag. Each iteration narrows
|
|
1847
1933
|
// `#max_search_index` to the next closer so a child's delimiter scan
|
|
@@ -1860,7 +1946,7 @@ export class MdzLexer {
|
|
|
1860
1946
|
type: 'tag_close',
|
|
1861
1947
|
name: tag_name,
|
|
1862
1948
|
start: close_start,
|
|
1863
|
-
end: this.#index
|
|
1949
|
+
end: this.#index
|
|
1864
1950
|
});
|
|
1865
1951
|
return;
|
|
1866
1952
|
}
|
|
@@ -1882,8 +1968,15 @@ export class MdzLexer {
|
|
|
1882
1968
|
// (the `#revert_tokens_from_link_open` pattern; the old fallthrough
|
|
1883
1969
|
// left a dangling `tag_open` whose parser fallback discarded content)
|
|
1884
1970
|
this.#max_search_index = saved_max;
|
|
1885
|
-
this.#failed_tag.add(`${start}:${bound}`);
|
|
1886
1971
|
this.#tokens.splice(open_token_index);
|
|
1972
|
+
this.#reject_tag(start, bound);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// a tag open at `start` that can't commit: memoizes the failure for this
|
|
1976
|
+
// `bound` so re-entries bail fast, then emits the `<` as text and resumes
|
|
1977
|
+
// scanning after it — the rest re-parses as ordinary content
|
|
1978
|
+
#reject_tag(start: number, bound: number): void {
|
|
1979
|
+
this.#failed_tag.add(`${start}:${bound}`);
|
|
1887
1980
|
this.#index = start + 1;
|
|
1888
1981
|
this.#emit_text('<', start);
|
|
1889
1982
|
}
|
|
@@ -2016,7 +2109,7 @@ export class MdzLexer {
|
|
|
2016
2109
|
parts === null
|
|
2017
2110
|
? this.#text.slice(start, this.#index)
|
|
2018
2111
|
: parts.join('') + this.#text.slice(part_start, this.#index);
|
|
2019
|
-
this.#tokens.push({type: 'text', content, start, end: this.#index});
|
|
2112
|
+
this.#tokens.push({ type: 'text', content, start, end: this.#index });
|
|
2020
2113
|
}
|
|
2021
2114
|
|
|
2022
2115
|
// -- Auto-link tokenizers --
|
|
@@ -2045,7 +2138,7 @@ export class MdzLexer {
|
|
|
2045
2138
|
reference,
|
|
2046
2139
|
link_type: 'external',
|
|
2047
2140
|
start,
|
|
2048
|
-
end: this.#index
|
|
2141
|
+
end: this.#index
|
|
2049
2142
|
});
|
|
2050
2143
|
}
|
|
2051
2144
|
|
|
@@ -2070,14 +2163,14 @@ export class MdzLexer {
|
|
|
2070
2163
|
reference,
|
|
2071
2164
|
link_type: 'internal',
|
|
2072
2165
|
start,
|
|
2073
|
-
end: this.#index
|
|
2166
|
+
end: this.#index
|
|
2074
2167
|
});
|
|
2075
2168
|
}
|
|
2076
2169
|
|
|
2077
2170
|
// -- Helper methods --
|
|
2078
2171
|
|
|
2079
2172
|
#emit_text(content: string, start: number): void {
|
|
2080
|
-
this.#tokens.push({type: 'text', content, start, end: start + content.length});
|
|
2173
|
+
this.#tokens.push({ type: 'text', content, start, end: start + content.length });
|
|
2081
2174
|
}
|
|
2082
2175
|
|
|
2083
2176
|
#has_paragraph_break_between(from: number, to: number): boolean {
|
|
@@ -2116,7 +2209,7 @@ export class MdzLexer {
|
|
|
2116
2209
|
i = this.#text.indexOf('\n', j);
|
|
2117
2210
|
}
|
|
2118
2211
|
if (memo === null) {
|
|
2119
|
-
this.#break_memo = {from, result};
|
|
2212
|
+
this.#break_memo = { from, result };
|
|
2120
2213
|
} else {
|
|
2121
2214
|
memo.from = from;
|
|
2122
2215
|
memo.result = result;
|
package/src/lib/mdz_opcodes.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type {MdzTableAlign} from './mdz.ts';
|
|
15
|
+
import type { MdzAttribute, MdzTableAlign } from './mdz.ts';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Unique monotonic identifier for each node created by the parser.
|
|
@@ -66,6 +66,8 @@ export interface MdzOpcodeOpen {
|
|
|
66
66
|
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
67
67
|
/** Tag name. Present when `node_type` is `'Element'` or `'Component'`. */
|
|
68
68
|
name?: string;
|
|
69
|
+
/** Parsed attributes. Present (possibly empty) when `node_type` is `'Element'` or `'Component'`. */
|
|
70
|
+
attributes?: Array<MdzAttribute>;
|
|
69
71
|
/** Language hint. Present when `node_type` is `'Codeblock'`. */
|
|
70
72
|
lang?: string | null;
|
|
71
73
|
/** Whether the list is ordered. Present when `node_type` is `'List'`. */
|