securemark 0.296.3 → 0.296.5
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/CHANGELOG.md +8 -0
- package/design.md +3 -4
- package/dist/index.js +146 -82
- package/markdown.d.ts +8 -4
- package/package.json +1 -1
- package/src/combinator/control/constraint/block.ts +12 -6
- package/src/combinator/control/manipulation/recovery.ts +3 -3
- package/src/combinator/data/parser.ts +7 -0
- package/src/parser/api/bind.ts +10 -5
- package/src/parser/api/parse.ts +3 -2
- package/src/parser/block/extension/fig.ts +3 -1
- package/src/parser/block/extension/figure.ts +2 -1
- package/src/parser/block/extension/message.ts +1 -1
- package/src/parser/block/extension.test.ts +3 -3
- package/src/parser/block/extension.ts +3 -4
- package/src/parser/block/heading.ts +4 -4
- package/src/parser/block.ts +22 -12
- package/src/parser/context.ts +13 -0
- package/src/parser/inline/autolink/account.ts +1 -1
- package/src/parser/inline/autolink/anchor.ts +1 -1
- package/src/parser/inline/autolink/hashnum.ts +1 -1
- package/src/parser/inline/autolink/hashtag.ts +1 -1
- package/src/parser/inline/emstrong.ts +1 -1
- package/src/parser/inline/extension/indexee.ts +8 -8
- package/src/parser/inline/mark.ts +1 -1
- package/src/parser/inline.test.ts +0 -1
- package/src/parser/processor/note.test.ts +35 -29
- package/src/parser/processor/note.ts +32 -23
- package/src/parser/segment.test.ts +73 -73
- package/src/parser/segment.ts +9 -11
- package/src/parser/source/line.ts +25 -6
- package/src/parser/source/str.ts +9 -10
- package/src/parser/source.ts +2 -1
|
@@ -28,7 +28,7 @@ function build(
|
|
|
28
28
|
splitter &&= `${splitter}, .${syntax}s`;
|
|
29
29
|
// Referenceを含むAnnotationの重複排除は両構文が互いに処理済みであることを必要とするため
|
|
30
30
|
// 構文ごとに各1回の処理では不可能
|
|
31
|
-
const
|
|
31
|
+
const refInfo = memoize((ref: HTMLElement): {
|
|
32
32
|
readonly content: Element;
|
|
33
33
|
readonly identifier: string;
|
|
34
34
|
readonly abbr: string;
|
|
@@ -37,6 +37,8 @@ function build(
|
|
|
37
37
|
const content = ref.firstElementChild!;
|
|
38
38
|
content.replaceWith(content.cloneNode());
|
|
39
39
|
const abbr = ref.getAttribute('data-abbr') ?? '';
|
|
40
|
+
const clone = content.cloneNode(true);
|
|
41
|
+
const txt = text(clone).trim();
|
|
40
42
|
const identifier = abbr
|
|
41
43
|
? identity(
|
|
42
44
|
'',
|
|
@@ -46,12 +48,12 @@ function build(
|
|
|
46
48
|
abbr.match(/^[^,\s]+(?:,? [^,\s]+)*?(?: \d{1,4}(?:-\d{0,4})?[a-z]?(?=,|$)|(?=,(?: [a-z]+\.?)? [0-9]))/)?.[0] ??
|
|
47
49
|
abbr
|
|
48
50
|
))?.slice(2) || ''
|
|
49
|
-
: identity('mark', undefined, signature(
|
|
51
|
+
: identity('mark', undefined, signature(clone))?.slice(6) || '';
|
|
50
52
|
return {
|
|
51
53
|
content,
|
|
52
54
|
identifier,
|
|
53
55
|
abbr,
|
|
54
|
-
text:
|
|
56
|
+
text: txt,
|
|
55
57
|
};
|
|
56
58
|
}, new WeakMap());
|
|
57
59
|
return function* (
|
|
@@ -62,10 +64,13 @@ function build(
|
|
|
62
64
|
): Generator<HTMLAnchorElement | HTMLLIElement | undefined, undefined, undefined> {
|
|
63
65
|
const defs = new Map<string, HTMLLIElement>();
|
|
64
66
|
const refs = target.querySelectorAll(`sup.${syntax}:not(.disabled)`);
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const identifierInfo = memoize((identifier: string) => ({
|
|
68
|
+
defIndex: 0,
|
|
69
|
+
defSubindex: 0,
|
|
70
|
+
refSubindex: 0,
|
|
71
|
+
title: '' && identifier,
|
|
72
|
+
queue: [] as HTMLElement[],
|
|
73
|
+
}));
|
|
69
74
|
const scope = target instanceof Element ? ':scope > ' : '';
|
|
70
75
|
const splitters = splitter ? target.querySelectorAll(`${scope}:is(${splitter})`) : [];
|
|
71
76
|
let iSplitters = 0;
|
|
@@ -100,17 +105,16 @@ function build(
|
|
|
100
105
|
assert(defs.size === 0);
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
|
-
const { content, identifier, abbr, text } =
|
|
104
|
-
const
|
|
105
|
-
|
|
108
|
+
const { content, identifier, abbr, text } = refInfo(ref);
|
|
109
|
+
const info = identifierInfo(identifier);
|
|
110
|
+
const refSubindex = ++info.refSubindex;
|
|
106
111
|
const refId = opts.id !== ''
|
|
107
112
|
? `${syntax}:${opts.id ?? ''}:ref:${identifier}:${refSubindex}`
|
|
108
113
|
: undefined;
|
|
109
114
|
const initial = splitter
|
|
110
115
|
? !defs.has(identifier)
|
|
111
116
|
: refSubindex === 1;
|
|
112
|
-
const defSubindex =
|
|
113
|
-
initial && defSubindexes?.set(identifier, defSubindex);
|
|
117
|
+
const defSubindex = initial ? ++info.defSubindex : info.defSubindex;
|
|
114
118
|
const defId = opts.id !== ''
|
|
115
119
|
? `${syntax}:${opts.id ?? ''}:def:${identifier}${splitter && `:${defSubindex}`}`
|
|
116
120
|
: undefined;
|
|
@@ -125,11 +129,9 @@ function build(
|
|
|
125
129
|
initial && defs.set(identifier, def);
|
|
126
130
|
assert(def.lastElementChild?.matches('sup'));
|
|
127
131
|
const defIndex = initial
|
|
128
|
-
? total + defs.size
|
|
129
|
-
:
|
|
130
|
-
|
|
131
|
-
const title = initial ? text : titles.get(identifier)!;
|
|
132
|
-
initial && titles.set(identifier, title);
|
|
132
|
+
? info.defIndex = total + defs.size
|
|
133
|
+
: info.defIndex;
|
|
134
|
+
const title = info.title ||= text;
|
|
133
135
|
assert(syntax !== 'annotation' || title);
|
|
134
136
|
ref.childElementCount > 1 && ref.lastElementChild!.remove();
|
|
135
137
|
define(ref, {
|
|
@@ -137,6 +139,14 @@ function build(
|
|
|
137
139
|
class: opts.id !== '' ? undefined : void ref.classList.add('disabled'),
|
|
138
140
|
title,
|
|
139
141
|
});
|
|
142
|
+
if (title && info.queue.length > 0) {
|
|
143
|
+
for (const ref of info.queue) {
|
|
144
|
+
define(ref, { title });
|
|
145
|
+
unmarkInvalid(ref);
|
|
146
|
+
}
|
|
147
|
+
info.queue = [];
|
|
148
|
+
def.firstElementChild!.replaceWith(content.cloneNode(true));
|
|
149
|
+
}
|
|
140
150
|
switch (ref.getAttribute('data-invalid-syntax')) {
|
|
141
151
|
case 'format':
|
|
142
152
|
case 'content':
|
|
@@ -144,11 +154,12 @@ function build(
|
|
|
144
154
|
}
|
|
145
155
|
format ??= abbr ? 'abbr' : 'number';
|
|
146
156
|
if (!ref.classList.contains('invalid')) switch (true) {
|
|
147
|
-
case format === 'number' ?
|
|
157
|
+
case format === 'number' ? abbr !== '' : abbr === '':
|
|
148
158
|
markInvalid(ref, syntax, 'format', 'Notation format must be consistent with numbers or abbreviations');
|
|
149
159
|
break;
|
|
150
|
-
case
|
|
160
|
+
case title === '':
|
|
151
161
|
markInvalid(ref, syntax, 'content', 'Missing the content');
|
|
162
|
+
info.queue.push(ref);
|
|
152
163
|
break;
|
|
153
164
|
}
|
|
154
165
|
yield ref.appendChild(html('a', { href: refId && defId && `#${defId}` }, marker(defIndex, abbr)));
|
|
@@ -167,11 +178,9 @@ function build(
|
|
|
167
178
|
? (++iSplitters, el as HTMLOListElement)
|
|
168
179
|
: target.insertBefore(html('ol', { class: `${syntax}s` }), splitters[iSplitters] ?? bottom);
|
|
169
180
|
yield* proc(defs, note);
|
|
181
|
+
assert(defs.size === 0);
|
|
170
182
|
}
|
|
171
|
-
if (splitter) for (
|
|
172
|
-
let el: Element;
|
|
173
|
-
el = splitters[iSplitters];
|
|
174
|
-
++iSplitters) {
|
|
183
|
+
if (splitter) for (let el: Element; el = splitters[iSplitters]; ++iSplitters) {
|
|
175
184
|
if (~iSplitters << 32 - 8 === 0) yield;
|
|
176
185
|
if (!scope && el.parentNode !== target) continue;
|
|
177
186
|
if (el.tagName === 'OL') {
|
|
@@ -4,108 +4,108 @@ import { Command } from './context';
|
|
|
4
4
|
describe('Unit: parser/segment', () => {
|
|
5
5
|
describe('segment', () => {
|
|
6
6
|
it('huge input', () => {
|
|
7
|
-
const result = segment(`${'\n'.repeat(1e6 + 1)}`).next().value?.split('\n', 1)[0];
|
|
7
|
+
const result = segment(`${'\n'.repeat(1e6 + 1)}`).next().value?.[0].split('\n', 1)[0];
|
|
8
8
|
assert(result?.startsWith(`${Command.Error}Too large input`));
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
it('huge segment', () => {
|
|
12
|
-
const result = segment(`${'\n'.repeat(1e5 + 1)}`).next().value?.split('\n', 1)[0];
|
|
12
|
+
const result = segment(`${'\n'.repeat(1e5 + 1)}`).next().value?.[0].split('\n', 1)[0];
|
|
13
13
|
assert(result?.startsWith(`${Command.Error}Too large segment`));
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it('basic', () => {
|
|
17
|
-
assert.deepStrictEqual([...segment('')], []);
|
|
18
|
-
assert.deepStrictEqual([...segment('a')], ['a']);
|
|
19
|
-
assert.deepStrictEqual([...segment('a\n')], ['a\n']);
|
|
20
|
-
assert.deepStrictEqual([...segment('a\n\n')], ['a\n', '\n']);
|
|
21
|
-
assert.deepStrictEqual([...segment('a\n\n\n')], ['a\n', '\n\n']);
|
|
22
|
-
assert.deepStrictEqual([...segment('a\nb')], ['a\nb']);
|
|
23
|
-
assert.deepStrictEqual([...segment('a\nb\n')], ['a\nb\n']);
|
|
24
|
-
assert.deepStrictEqual([...segment('a\nb\n\n')], ['a\nb\n', '\n']);
|
|
25
|
-
assert.deepStrictEqual([...segment('a\nb\n\n\n')], ['a\nb\n', '\n\n']);
|
|
26
|
-
assert.deepStrictEqual([...segment('a\nb\n\nc\n\nd')], ['a\nb\n', '\n', 'c\n', '\n', 'd']);
|
|
27
|
-
assert.deepStrictEqual([...segment('a\n\\\nb')], ['a\n\\\nb']);
|
|
28
|
-
assert.deepStrictEqual([...segment('a ')], ['a ']);
|
|
29
|
-
assert.deepStrictEqual([...segment(' a')], [' a']);
|
|
30
|
-
assert.deepStrictEqual([...segment(' a ')], [' a ']);
|
|
17
|
+
assert.deepStrictEqual([...segment('')].map(t => t[0]), []);
|
|
18
|
+
assert.deepStrictEqual([...segment('a')].map(t => t[0]), ['a']);
|
|
19
|
+
assert.deepStrictEqual([...segment('a\n')].map(t => t[0]), ['a\n']);
|
|
20
|
+
assert.deepStrictEqual([...segment('a\n\n')].map(t => t[0]), ['a\n', '\n']);
|
|
21
|
+
assert.deepStrictEqual([...segment('a\n\n\n')].map(t => t[0]), ['a\n', '\n\n']);
|
|
22
|
+
assert.deepStrictEqual([...segment('a\nb')].map(t => t[0]), ['a\nb']);
|
|
23
|
+
assert.deepStrictEqual([...segment('a\nb\n')].map(t => t[0]), ['a\nb\n']);
|
|
24
|
+
assert.deepStrictEqual([...segment('a\nb\n\n')].map(t => t[0]), ['a\nb\n', '\n']);
|
|
25
|
+
assert.deepStrictEqual([...segment('a\nb\n\n\n')].map(t => t[0]), ['a\nb\n', '\n\n']);
|
|
26
|
+
assert.deepStrictEqual([...segment('a\nb\n\nc\n\nd')].map(t => t[0]), ['a\nb\n', '\n', 'c\n', '\n', 'd']);
|
|
27
|
+
assert.deepStrictEqual([...segment('a\n\\\nb')].map(t => t[0]), ['a\n\\\nb']);
|
|
28
|
+
assert.deepStrictEqual([...segment('a ')].map(t => t[0]), ['a ']);
|
|
29
|
+
assert.deepStrictEqual([...segment(' a')].map(t => t[0]), [' a']);
|
|
30
|
+
assert.deepStrictEqual([...segment(' a ')].map(t => t[0]), [' a ']);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
it('linebreak', () => {
|
|
34
|
-
assert.deepStrictEqual([...segment('\n')], ['\n']);
|
|
35
|
-
assert.deepStrictEqual([...segment('\n\n')], ['\n\n']);
|
|
36
|
-
assert.deepStrictEqual([...segment('\n\n\n')], ['\n\n\n']);
|
|
37
|
-
assert.deepStrictEqual([...segment('\n\n\n\n')], ['\n\n\n\n']);
|
|
38
|
-
assert.deepStrictEqual([...segment(' ')], [' ']);
|
|
39
|
-
assert.deepStrictEqual([...segment(' \n')], [' \n']);
|
|
40
|
-
assert.deepStrictEqual([...segment(' \n \n \n ')], [' \n \n \n ']);
|
|
41
|
-
assert.deepStrictEqual([...segment('a\n')], ['a\n']);
|
|
42
|
-
assert.deepStrictEqual([...segment('a\n ')], ['a\n', ' ']);
|
|
43
|
-
assert.deepStrictEqual([...segment('a\n\n ')], ['a\n', '\n ']);
|
|
44
|
-
assert.deepStrictEqual([...segment('a\n\n\n ')], ['a\n', '\n\n ']);
|
|
45
|
-
assert.deepStrictEqual([...segment('a\n\n\n\n ')], ['a\n', '\n\n\n ']);
|
|
46
|
-
assert.deepStrictEqual([...segment('a\n\n\n\n\n ')], ['a\n', '\n\n\n\n ']);
|
|
47
|
-
assert.deepStrictEqual([...segment('a\n\n\n\n\n\n ')], ['a\n', '\n\n\n\n\n ']);
|
|
34
|
+
assert.deepStrictEqual([...segment('\n')].map(t => t[0]), ['\n']);
|
|
35
|
+
assert.deepStrictEqual([...segment('\n\n')].map(t => t[0]), ['\n\n']);
|
|
36
|
+
assert.deepStrictEqual([...segment('\n\n\n')].map(t => t[0]), ['\n\n\n']);
|
|
37
|
+
assert.deepStrictEqual([...segment('\n\n\n\n')].map(t => t[0]), ['\n\n\n\n']);
|
|
38
|
+
assert.deepStrictEqual([...segment(' ')].map(t => t[0]), [' ']);
|
|
39
|
+
assert.deepStrictEqual([...segment(' \n')].map(t => t[0]), [' \n']);
|
|
40
|
+
assert.deepStrictEqual([...segment(' \n \n \n ')].map(t => t[0]), [' \n \n \n ']);
|
|
41
|
+
assert.deepStrictEqual([...segment('a\n')].map(t => t[0]), ['a\n']);
|
|
42
|
+
assert.deepStrictEqual([...segment('a\n ')].map(t => t[0]), ['a\n', ' ']);
|
|
43
|
+
assert.deepStrictEqual([...segment('a\n\n ')].map(t => t[0]), ['a\n', '\n ']);
|
|
44
|
+
assert.deepStrictEqual([...segment('a\n\n\n ')].map(t => t[0]), ['a\n', '\n\n ']);
|
|
45
|
+
assert.deepStrictEqual([...segment('a\n\n\n\n ')].map(t => t[0]), ['a\n', '\n\n\n ']);
|
|
46
|
+
assert.deepStrictEqual([...segment('a\n\n\n\n\n ')].map(t => t[0]), ['a\n', '\n\n\n\n ']);
|
|
47
|
+
assert.deepStrictEqual([...segment('a\n\n\n\n\n\n ')].map(t => t[0]), ['a\n', '\n\n\n\n\n ']);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
it('codeblock', () => {
|
|
51
|
-
assert.deepStrictEqual([...segment('```')], ['```']);
|
|
52
|
-
assert.deepStrictEqual([...segment('```\n```')], ['```\n```']);
|
|
53
|
-
assert.deepStrictEqual([...segment('```\n\n```')], ['```\n\n```']);
|
|
54
|
-
assert.deepStrictEqual([...segment('```\n\n\n```')], ['```\n', '\n\n', '```']);
|
|
55
|
-
assert.deepStrictEqual([...segment('```\n\n0\n```')], ['```\n', '\n', '0\n```']);
|
|
56
|
-
assert.deepStrictEqual([...segment('```\n0\n\n```')], ['```\n0\n\n```']);
|
|
57
|
-
assert.deepStrictEqual([...segment('```\n````\n```')], ['```\n````\n```']);
|
|
58
|
-
assert.deepStrictEqual([...segment('````\n```\n````')], ['````\n```\n````']);
|
|
59
|
-
assert.deepStrictEqual([...segment('```\n\n```\n\n')], ['```\n\n```\n', '\n']);
|
|
51
|
+
assert.deepStrictEqual([...segment('```')].map(t => t[0]), ['```']);
|
|
52
|
+
assert.deepStrictEqual([...segment('```\n```')].map(t => t[0]), ['```\n```']);
|
|
53
|
+
assert.deepStrictEqual([...segment('```\n\n```')].map(t => t[0]), ['```\n\n```']);
|
|
54
|
+
assert.deepStrictEqual([...segment('```\n\n\n```')].map(t => t[0]), ['```\n', '\n\n', '```']);
|
|
55
|
+
assert.deepStrictEqual([...segment('```\n\n0\n```')].map(t => t[0]), ['```\n', '\n', '0\n```']);
|
|
56
|
+
assert.deepStrictEqual([...segment('```\n0\n\n```')].map(t => t[0]), ['```\n0\n\n```']);
|
|
57
|
+
assert.deepStrictEqual([...segment('```\n````\n```')].map(t => t[0]), ['```\n````\n```']);
|
|
58
|
+
assert.deepStrictEqual([...segment('````\n```\n````')].map(t => t[0]), ['````\n```\n````']);
|
|
59
|
+
assert.deepStrictEqual([...segment('```\n\n```\n\n')].map(t => t[0]), ['```\n\n```\n', '\n']);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it('mathblock', () => {
|
|
63
|
-
assert.deepStrictEqual([...segment('$$')], ['$$']);
|
|
64
|
-
assert.deepStrictEqual([...segment('$$\n$$')], ['$$\n$$']);
|
|
65
|
-
assert.deepStrictEqual([...segment('$$\n\n$$')], ['$$\n\n$$']);
|
|
66
|
-
assert.deepStrictEqual([...segment('$$\n\n\n$$')], ['$$\n', '\n\n', '$$']);
|
|
67
|
-
assert.deepStrictEqual([...segment('$$\n\n0\n$$')], ['$$\n', '\n', '0\n$$']);
|
|
68
|
-
assert.deepStrictEqual([...segment('$$\n0\n\n$$')], ['$$\n0\n\n$$']);
|
|
69
|
-
assert.deepStrictEqual([...segment('$$\n\n$$\n\n')], ['$$\n\n$$\n', '\n']);
|
|
63
|
+
assert.deepStrictEqual([...segment('$$')].map(t => t[0]), ['$$']);
|
|
64
|
+
assert.deepStrictEqual([...segment('$$\n$$')].map(t => t[0]), ['$$\n$$']);
|
|
65
|
+
assert.deepStrictEqual([...segment('$$\n\n$$')].map(t => t[0]), ['$$\n\n$$']);
|
|
66
|
+
assert.deepStrictEqual([...segment('$$\n\n\n$$')].map(t => t[0]), ['$$\n', '\n\n', '$$']);
|
|
67
|
+
assert.deepStrictEqual([...segment('$$\n\n0\n$$')].map(t => t[0]), ['$$\n', '\n', '0\n$$']);
|
|
68
|
+
assert.deepStrictEqual([...segment('$$\n0\n\n$$')].map(t => t[0]), ['$$\n0\n\n$$']);
|
|
69
|
+
assert.deepStrictEqual([...segment('$$\n\n$$\n\n')].map(t => t[0]), ['$$\n\n$$\n', '\n']);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it('extension', () => {
|
|
73
|
-
assert.deepStrictEqual([...segment('~~~')], ['~~~']);
|
|
74
|
-
assert.deepStrictEqual([...segment('~~~\n~~~')], ['~~~\n~~~']);
|
|
75
|
-
assert.deepStrictEqual([...segment('~~~\n\n~~~')], ['~~~\n\n~~~']);
|
|
76
|
-
assert.deepStrictEqual([...segment('~~~\n\n\n~~~')], ['~~~\n', '\n\n', '~~~']);
|
|
77
|
-
assert.deepStrictEqual([...segment('~~~\n\n0\n~~~')], ['~~~\n', '\n', '0\n~~~']);
|
|
78
|
-
assert.deepStrictEqual([...segment('~~~\n0\n\n~~~')], ['~~~\n0\n\n~~~']);
|
|
79
|
-
assert.deepStrictEqual([...segment('~~~\n~~~~\n~~~')], ['~~~\n~~~~\n~~~']);
|
|
80
|
-
assert.deepStrictEqual([...segment('~~~~\n~~~\n~~~~')], ['~~~~\n~~~\n~~~~']);
|
|
81
|
-
assert.deepStrictEqual([...segment('~~~\n\n~~~\n\n')], ['~~~\n\n~~~\n', '\n']);
|
|
82
|
-
assert.deepStrictEqual([...segment('~~~\n```\n~~~\n```\n~~~')], ['~~~\n```\n~~~\n```\n~~~']);
|
|
83
|
-
assert.deepStrictEqual([...segment('~~~\ninvalid\n~~~')], ['~~~\ninvalid\n~~~']);
|
|
84
|
-
assert.deepStrictEqual([...segment('~~~\ninvalid\n\ncaption\n~~~')], ['~~~\ninvalid\n\ncaption\n~~~']);
|
|
85
|
-
assert.deepStrictEqual([...segment('~~~figure [$-name]\n$$\n$$\n\n```\n~~~')], ['~~~figure [$-name]\n$$\n$$\n\n```\n~~~']);
|
|
73
|
+
assert.deepStrictEqual([...segment('~~~')].map(t => t[0]), ['~~~']);
|
|
74
|
+
assert.deepStrictEqual([...segment('~~~\n~~~')].map(t => t[0]), ['~~~\n~~~']);
|
|
75
|
+
assert.deepStrictEqual([...segment('~~~\n\n~~~')].map(t => t[0]), ['~~~\n\n~~~']);
|
|
76
|
+
assert.deepStrictEqual([...segment('~~~\n\n\n~~~')].map(t => t[0]), ['~~~\n', '\n\n', '~~~']);
|
|
77
|
+
assert.deepStrictEqual([...segment('~~~\n\n0\n~~~')].map(t => t[0]), ['~~~\n', '\n', '0\n~~~']);
|
|
78
|
+
assert.deepStrictEqual([...segment('~~~\n0\n\n~~~')].map(t => t[0]), ['~~~\n0\n\n~~~']);
|
|
79
|
+
assert.deepStrictEqual([...segment('~~~\n~~~~\n~~~')].map(t => t[0]), ['~~~\n~~~~\n~~~']);
|
|
80
|
+
assert.deepStrictEqual([...segment('~~~~\n~~~\n~~~~')].map(t => t[0]), ['~~~~\n~~~\n~~~~']);
|
|
81
|
+
assert.deepStrictEqual([...segment('~~~\n\n~~~\n\n')].map(t => t[0]), ['~~~\n\n~~~\n', '\n']);
|
|
82
|
+
assert.deepStrictEqual([...segment('~~~\n```\n~~~\n```\n~~~')].map(t => t[0]), ['~~~\n```\n~~~\n```\n~~~']);
|
|
83
|
+
assert.deepStrictEqual([...segment('~~~\ninvalid\n~~~')].map(t => t[0]), ['~~~\ninvalid\n~~~']);
|
|
84
|
+
assert.deepStrictEqual([...segment('~~~\ninvalid\n\ncaption\n~~~')].map(t => t[0]), ['~~~\ninvalid\n\ncaption\n~~~']);
|
|
85
|
+
assert.deepStrictEqual([...segment('~~~figure [$-name]\n$$\n$$\n\n```\n~~~')].map(t => t[0]), ['~~~figure [$-name]\n$$\n$$\n\n```\n~~~']);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('mixed', () => {
|
|
89
|
-
assert.deepStrictEqual([...segment('```\n\n\n')], ['```\n', '\n\n']);
|
|
90
|
-
assert.deepStrictEqual([...segment('~~~\n\n\n')], ['~~~\n', '\n\n']);
|
|
91
|
-
assert.deepStrictEqual([...segment('```\n~~~\n```')], ['```\n~~~\n```']);
|
|
92
|
-
assert.deepStrictEqual([...segment('~~~\n```\n~~~')], ['~~~\n```\n~~~']);
|
|
93
|
-
assert.deepStrictEqual([...segment('```\n```\n\n~~~\n~~~')], ['```\n```\n', '\n', '~~~\n~~~']);
|
|
94
|
-
assert.deepStrictEqual([...segment('~~~\n~~~\n\n```\n```')], ['~~~\n~~~\n', '\n', '```\n```']);
|
|
95
|
-
assert.deepStrictEqual([...segment(' ```\n\n```')], [' ```\n', '\n', '```']);
|
|
96
|
-
assert.deepStrictEqual([...segment(' ~~~\n\n~~~')], [' ~~~\n', '\n', '~~~']);
|
|
89
|
+
assert.deepStrictEqual([...segment('```\n\n\n')].map(t => t[0]), ['```\n', '\n\n']);
|
|
90
|
+
assert.deepStrictEqual([...segment('~~~\n\n\n')].map(t => t[0]), ['~~~\n', '\n\n']);
|
|
91
|
+
assert.deepStrictEqual([...segment('```\n~~~\n```')].map(t => t[0]), ['```\n~~~\n```']);
|
|
92
|
+
assert.deepStrictEqual([...segment('~~~\n```\n~~~')].map(t => t[0]), ['~~~\n```\n~~~']);
|
|
93
|
+
assert.deepStrictEqual([...segment('```\n```\n\n~~~\n~~~')].map(t => t[0]), ['```\n```\n', '\n', '~~~\n~~~']);
|
|
94
|
+
assert.deepStrictEqual([...segment('~~~\n~~~\n\n```\n```')].map(t => t[0]), ['~~~\n~~~\n', '\n', '```\n```']);
|
|
95
|
+
assert.deepStrictEqual([...segment(' ```\n\n```')].map(t => t[0]), [' ```\n', '\n', '```']);
|
|
96
|
+
assert.deepStrictEqual([...segment(' ~~~\n\n~~~')].map(t => t[0]), [' ~~~\n', '\n', '~~~']);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
it('blockquote', () => {
|
|
100
|
-
assert.deepStrictEqual([...segment('> ```\n\n```')], ['> ```\n', '\n', '```']);
|
|
101
|
-
assert.deepStrictEqual([...segment('!> ```\n\n```')], ['!> ```\n', '\n', '```']);
|
|
100
|
+
assert.deepStrictEqual([...segment('> ```\n\n```')].map(t => t[0]), ['> ```\n', '\n', '```']);
|
|
101
|
+
assert.deepStrictEqual([...segment('!> ```\n\n```')].map(t => t[0]), ['!> ```\n', '\n', '```']);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
it('heading', () => {
|
|
105
|
-
assert.deepStrictEqual([...segment('# a\n\n# b\n')], ['# a\n', '\n', '# b\n']);
|
|
106
|
-
assert.deepStrictEqual([...segment('# a\n# b\n')], ['# a\n', '# b\n']);
|
|
107
|
-
assert.deepStrictEqual([...segment('# a\n# b')], ['# a\n', '# b']);
|
|
108
|
-
assert.deepStrictEqual([...segment('# a\n # b')], ['# a\n # b']);
|
|
105
|
+
assert.deepStrictEqual([...segment('# a\n\n# b\n')].map(t => t[0]), ['# a\n', '\n', '# b\n']);
|
|
106
|
+
assert.deepStrictEqual([...segment('# a\n# b\n')].map(t => t[0]), ['# a\n', '# b\n']);
|
|
107
|
+
assert.deepStrictEqual([...segment('# a\n# b')].map(t => t[0]), ['# a\n', '# b']);
|
|
108
|
+
assert.deepStrictEqual([...segment('# a\n # b')].map(t => t[0]), ['# a\n # b']);
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
});
|
package/src/parser/segment.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { MarkdownParser } from '../../markdown';
|
|
2
|
-
import { Context, Command } from './context';
|
|
2
|
+
import { Context, Segment, Command } from './context';
|
|
3
3
|
import { union, some } from '../combinator';
|
|
4
4
|
import { segment as heading } from './block/heading';
|
|
5
5
|
import { segment as codeblock } from './block/codeblock';
|
|
6
6
|
import { segment as mathblock } from './block/mathblock';
|
|
7
7
|
import { segment as extension } from './block/extension';
|
|
8
|
-
import { contentline,
|
|
8
|
+
import { contentline, emptysegment } from './source';
|
|
9
9
|
|
|
10
10
|
import SegmentParser = MarkdownParser.SegmentParser;
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ export const MAX_SEGMENT_SIZE = 100_000; // 100,000 bytes (Max value size of FDB
|
|
|
13
13
|
export const MAX_INPUT_SIZE = MAX_SEGMENT_SIZE * 10;
|
|
14
14
|
|
|
15
15
|
const parser: SegmentParser = union([
|
|
16
|
-
some(
|
|
16
|
+
some(emptysegment, MAX_SEGMENT_SIZE + 1),
|
|
17
17
|
input => {
|
|
18
18
|
const { context: { source, position } } = input;
|
|
19
19
|
if (position === source.length) return;
|
|
@@ -26,21 +26,19 @@ const parser: SegmentParser = union([
|
|
|
26
26
|
break;
|
|
27
27
|
case '$':
|
|
28
28
|
if (source[position + 1] === '$') return mathblock(input);
|
|
29
|
-
|
|
29
|
+
return extension(input);
|
|
30
30
|
case '[':
|
|
31
31
|
if (source[position + 1] === '$') return extension(input);
|
|
32
32
|
break;
|
|
33
33
|
case '#':
|
|
34
34
|
return heading(input);
|
|
35
|
-
case '$':
|
|
36
|
-
return extension(input);
|
|
37
35
|
}
|
|
38
36
|
},
|
|
39
37
|
some(contentline, MAX_SEGMENT_SIZE + 1),
|
|
40
38
|
]) as any;
|
|
41
39
|
|
|
42
|
-
export function* segment(source: string): Generator<string, undefined, undefined> {
|
|
43
|
-
if (!validate(source, MAX_INPUT_SIZE)) return yield `${Command.Error}Too large input over ${MAX_INPUT_SIZE.toLocaleString('en')} bytes.\n${source.slice(0, 1001)}
|
|
40
|
+
export function* segment(source: string): Generator<readonly [string, Segment], undefined, undefined> {
|
|
41
|
+
if (!validate(source, MAX_INPUT_SIZE)) return yield [`${Command.Error}Too large input over ${MAX_INPUT_SIZE.toLocaleString('en')} bytes.\n${source.slice(0, 1001)}`, Segment.unknown];
|
|
44
42
|
assert(source.length < Number.MAX_SAFE_INTEGER);
|
|
45
43
|
for (let position = 0; position < source.length;) {
|
|
46
44
|
const context = new Context({ source, position });
|
|
@@ -51,13 +49,13 @@ export function* segment(source: string): Generator<string, undefined, undefined
|
|
|
51
49
|
? result.foldl<string[]>((acc, { value }) => void acc.push(value) || acc, [])
|
|
52
50
|
: [source.slice(position, context.position)];
|
|
53
51
|
assert(segs.join('') === source.slice(position, context.position));
|
|
52
|
+
position = context.position;
|
|
54
53
|
for (let i = 0; i < segs.length; ++i) {
|
|
55
54
|
const seg = segs[i];
|
|
56
55
|
validate(seg, MAX_SEGMENT_SIZE)
|
|
57
|
-
? yield seg
|
|
58
|
-
: yield `${Command.Error}Too large segment over ${MAX_SEGMENT_SIZE.toLocaleString('en')} bytes.\n${seg}
|
|
56
|
+
? yield [seg, context.segment]
|
|
57
|
+
: yield [`${Command.Error}Too large segment over ${MAX_SEGMENT_SIZE.toLocaleString('en')} bytes.\n${seg}`, Segment.unknown];
|
|
59
58
|
}
|
|
60
|
-
position = context.position;
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
61
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AnyLineParser, EmptyLineParser, ContentLineParser } from '../source';
|
|
1
|
+
import { AnyLineParser, EmptyLineParser, EmptySegmentParser, ContentLineParser } from '../source';
|
|
2
|
+
import { Segment } from '../context';
|
|
2
3
|
import { List } from '../../combinator/data/parser';
|
|
3
4
|
|
|
4
5
|
export const anyline: AnyLineParser = input => {
|
|
@@ -14,14 +15,32 @@ export const emptyline: EmptyLineParser = input => {
|
|
|
14
15
|
const { context } = input;
|
|
15
16
|
const { source, position } = context;
|
|
16
17
|
if (position === source.length) return;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
const i = eoel(source, position);
|
|
19
|
+
if (i === position) return;
|
|
20
|
+
context.position = i;
|
|
21
|
+
return new List();
|
|
22
|
+
};
|
|
23
|
+
export const emptysegment: EmptySegmentParser = input => {
|
|
24
|
+
const { context } = input;
|
|
25
|
+
const { source, position, segment } = context;
|
|
26
|
+
if (position === source.length) return;
|
|
27
|
+
if (segment & Segment.write) {
|
|
28
|
+
if (segment !== (Segment.empty | Segment.write)) return;
|
|
29
|
+
context.position = source.length;
|
|
30
|
+
return new List();
|
|
31
|
+
}
|
|
32
|
+
const i = eoel(source, position);
|
|
33
|
+
if (i === position) return;
|
|
22
34
|
context.position = i;
|
|
35
|
+
context.segment = Segment.empty;
|
|
23
36
|
return new List();
|
|
24
37
|
};
|
|
38
|
+
function eoel(source: string, position: number): number {
|
|
39
|
+
if (source[position] === '\n') return position + 1;
|
|
40
|
+
regEmptyline.lastIndex = position;
|
|
41
|
+
regEmptyline.test(source);
|
|
42
|
+
return regEmptyline.lastIndex || position;
|
|
43
|
+
}
|
|
25
44
|
|
|
26
45
|
const regContentline = /[^\S\n]*\S[^\n]*(?:$|\n)/y;
|
|
27
46
|
export const contentline: ContentLineParser = input => {
|
package/src/parser/source/str.ts
CHANGED
|
@@ -7,18 +7,17 @@ export function str(pattern: string | RegExp, after?: string | RegExp): Parser<s
|
|
|
7
7
|
return matcher(pattern, true, after ? tester(after, false) : undefined);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function strs(
|
|
11
|
-
export function strs(
|
|
12
|
-
assert(
|
|
10
|
+
export function strs(char: string, min?: number, max?: number): StrParser;
|
|
11
|
+
export function strs(char: string, min: number = 1, max: number = -1): Parser<string> {
|
|
12
|
+
assert(char.length === 1);
|
|
13
13
|
return ({ context }) => {
|
|
14
|
-
const { source } = context;
|
|
15
|
-
let
|
|
16
|
-
for (
|
|
17
|
-
|
|
18
|
-
context.position += pattern.length;
|
|
14
|
+
const { source, position } = context;
|
|
15
|
+
let cnt = 0;
|
|
16
|
+
for (; cnt !== max && context.position < source.length && source[context.position] === char; ++cnt) {
|
|
17
|
+
context.position += char.length;
|
|
19
18
|
}
|
|
20
|
-
return
|
|
21
|
-
? new List([new Node(
|
|
19
|
+
return cnt >= min
|
|
20
|
+
? new List([new Node(source.slice(position, context.position))])
|
|
22
21
|
: undefined;
|
|
23
22
|
};
|
|
24
23
|
}
|
package/src/parser/source.ts
CHANGED
|
@@ -8,10 +8,11 @@ export import UnescapableSourceParser = SourceParser.UnescapableSourceParser;
|
|
|
8
8
|
export import StrParser = SourceParser.StrParser;
|
|
9
9
|
export import ContentLineParser = SourceParser.ContentLineParser;
|
|
10
10
|
export import EmptyLineParser = SourceParser.EmptyLineParser;
|
|
11
|
+
export import EmptySegmentParser = SourceParser.EmptySegmentParser;
|
|
11
12
|
export import AnyLineParser = SourceParser.AnyLineParser;
|
|
12
13
|
|
|
13
14
|
export { text, txt } from './source/text';
|
|
14
15
|
export { escsource } from './source/escapable';
|
|
15
16
|
export { unescsource } from './source/unescapable';
|
|
16
17
|
export { str, strs } from './source/str';
|
|
17
|
-
export { contentline, emptyline, anyline } from './source/line';
|
|
18
|
+
export { contentline, emptyline, emptysegment, anyline } from './source/line';
|