securemark 0.299.1 → 0.299.3
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/dist/index.js +192 -138
- package/markdown.d.ts +2 -2
- package/package.json +1 -1
- package/src/combinator/control/manipulation/fence.ts +2 -2
- package/src/combinator/control/manipulation/match.ts +2 -2
- package/src/combinator/data/delimiter.ts +5 -3
- package/src/combinator/data/parser/context.ts +12 -38
- package/src/combinator/data/parser/some.ts +13 -6
- package/src/parser/api/header.ts +5 -1
- package/src/parser/api/parse.test.ts +9 -9
- package/src/parser/block/blockquote.ts +2 -2
- package/src/parser/block.ts +1 -1
- package/src/parser/context.ts +5 -6
- package/src/parser/header.test.ts +5 -5
- package/src/parser/header.ts +2 -3
- package/src/parser/inline/annotation.ts +9 -4
- package/src/parser/inline/autolink/url.ts +3 -4
- package/src/parser/inline/deletion.ts +1 -1
- package/src/parser/inline/emstrong.ts +1 -1
- package/src/parser/inline/insertion.ts +1 -1
- package/src/parser/inline/italic.ts +1 -1
- package/src/parser/inline/link.ts +2 -2
- package/src/parser/inline/mark.ts +1 -1
- package/src/parser/inline/math.test.ts +2 -2
- package/src/parser/inline/math.ts +3 -3
- package/src/parser/inline/media.ts +2 -2
- package/src/parser/inline/ruby.ts +2 -3
- package/src/parser/inline.test.ts +1 -1
- package/src/parser/inline.ts +1 -1
- package/src/parser/repeat.ts +11 -23
- package/src/parser/source/escapable.ts +33 -10
- package/src/parser/source/text.ts +15 -24
- package/src/parser/source/unescapable.test.ts +1 -1
- package/src/parser/source/unescapable.ts +90 -9
package/markdown.d.ts
CHANGED
|
@@ -1082,7 +1082,7 @@ export namespace MarkdownParser {
|
|
|
1082
1082
|
Inline<'url'>,
|
|
1083
1083
|
Parser<string | HTMLElement, Context, [
|
|
1084
1084
|
Parser<HTMLAnchorElement, Context, []>,
|
|
1085
|
-
|
|
1085
|
+
Parser<string, Context, []>,
|
|
1086
1086
|
]> {
|
|
1087
1087
|
}
|
|
1088
1088
|
export namespace UrlParser {
|
|
@@ -1092,7 +1092,7 @@ export namespace MarkdownParser {
|
|
|
1092
1092
|
SourceParser.StrParser,
|
|
1093
1093
|
Parser<string | HTMLElement, Context, [
|
|
1094
1094
|
Parser<HTMLAnchorElement, Context, []>,
|
|
1095
|
-
|
|
1095
|
+
Parser<string, Context, []>,
|
|
1096
1096
|
]>,
|
|
1097
1097
|
]> {
|
|
1098
1098
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parser, List, Node, Context, failsafe } from '../../data/parser';
|
|
2
|
-
import {
|
|
2
|
+
import { spend } from '../../../combinator';
|
|
3
3
|
import { firstline, isEmptyline } from '../constraint/line';
|
|
4
4
|
import { push } from 'spica/array';
|
|
5
5
|
|
|
@@ -13,7 +13,7 @@ export function fence<C extends Context, D extends Parser<unknown, C>[]>(opener:
|
|
|
13
13
|
const matches = opener.exec(source);
|
|
14
14
|
if (!matches) return;
|
|
15
15
|
assert(matches[0] === firstline(source, position));
|
|
16
|
-
|
|
16
|
+
spend(context, matches[0].length);
|
|
17
17
|
const delim = matches[1];
|
|
18
18
|
assert(delim && delim === delim.trim());
|
|
19
19
|
if (matches[0].includes(delim, delim.length)) return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parser } from '../../data/parser';
|
|
2
|
-
import {
|
|
2
|
+
import { spend } from '../../../combinator';
|
|
3
3
|
|
|
4
4
|
export function match<P extends Parser>(pattern: RegExp, f: (matched: RegExpMatchArray) => P): P;
|
|
5
5
|
export function match<N>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<N>): Parser<N> {
|
|
@@ -15,7 +15,7 @@ export function match<N>(pattern: RegExp, f: (matched: RegExpMatchArray) => Pars
|
|
|
15
15
|
const params = pattern.exec(source);
|
|
16
16
|
if (!params) return;
|
|
17
17
|
assert(source.startsWith(params[0], position));
|
|
18
|
-
count &&
|
|
18
|
+
count && spend(context, params[0].length);
|
|
19
19
|
const result = f(params)(input);
|
|
20
20
|
context.position += result
|
|
21
21
|
? context.position === position
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parser, Input, List, Node, Context } from './parser';
|
|
2
|
-
import {
|
|
2
|
+
import { spend } from './parser/context';
|
|
3
3
|
|
|
4
4
|
interface Delimiter {
|
|
5
5
|
readonly memory: Delimiter[];
|
|
@@ -37,6 +37,8 @@ export class Delimiters {
|
|
|
37
37
|
assert(pattern !== '');
|
|
38
38
|
return index(`'${pattern}`);
|
|
39
39
|
case 'object':
|
|
40
|
+
assert(pattern.flags.includes('y'));
|
|
41
|
+
assert(/^yu?$/.test(pattern.flags));
|
|
40
42
|
return index(`/${pattern.source}`);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -197,7 +199,7 @@ export function matcher(pattern: string | RegExp, advance: boolean, after?: Pars
|
|
|
197
199
|
pos = position;
|
|
198
200
|
if (index === -1) return;
|
|
199
201
|
const src = source.slice(position, index);
|
|
200
|
-
count && !hit &&
|
|
202
|
+
count && !hit && spend(context, src.length);
|
|
201
203
|
if (advance) {
|
|
202
204
|
context.position = index;
|
|
203
205
|
}
|
|
@@ -248,7 +250,7 @@ export function tester(pattern: string | RegExp, advance: boolean, after?: Parse
|
|
|
248
250
|
pos = position;
|
|
249
251
|
if (index === -1) return;
|
|
250
252
|
const len = index - position;
|
|
251
|
-
count && !hit &&
|
|
253
|
+
count && !hit && spend(context, len);
|
|
252
254
|
if (advance) {
|
|
253
255
|
context.position = index;
|
|
254
256
|
}
|
|
@@ -6,61 +6,35 @@ export function creation(cost: number, parser: Parser): Parser {
|
|
|
6
6
|
assert(cost >= 0);
|
|
7
7
|
return input => {
|
|
8
8
|
const context = input;
|
|
9
|
-
const resources = context.resources ?? { clock: cost || 1, recursions: [1] };
|
|
10
|
-
const { recursions } = resources;
|
|
11
|
-
assert(recursions.length > 0);
|
|
12
9
|
const result = parser(input);
|
|
13
10
|
if (result === undefined) return;
|
|
14
|
-
|
|
11
|
+
spend(context, cost);
|
|
15
12
|
return result;
|
|
16
13
|
};
|
|
17
14
|
}
|
|
18
|
-
export function
|
|
19
|
-
const
|
|
20
|
-
if (!resources) return;
|
|
15
|
+
export function spend(context: Context, cost: number): void {
|
|
16
|
+
const resources = context.resources ?? { clock: cost || 1, recursions: [1] };
|
|
21
17
|
if (resources.clock < cost) throw new Error('Too many creations');
|
|
22
18
|
resources.clock -= cost;
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
export function recursion<P extends Parser>(
|
|
26
|
-
export function recursion(
|
|
27
|
-
assert(
|
|
21
|
+
export function recursion<P extends Parser>(index: number, parser: P): P;
|
|
22
|
+
export function recursion(index: number, parser: Parser): Parser {
|
|
23
|
+
assert(index >= 0);
|
|
28
24
|
return input => {
|
|
29
25
|
const context = input;
|
|
30
26
|
const resources = context.resources ?? { clock: 1, recursions: [1] };
|
|
31
27
|
const { recursions } = resources;
|
|
32
|
-
|
|
33
|
-
const rec = min(recursion, recursions.length - 1);
|
|
34
|
-
if (rec >= 0 && recursions[rec] < 1) throw new Error('Too much recursion');
|
|
35
|
-
rec >= 0 && --recursions[rec];
|
|
28
|
+
recur(recursions, index, 1);
|
|
36
29
|
const result = parser(input);
|
|
37
|
-
|
|
30
|
+
recur(recursions, index, -1);
|
|
38
31
|
return result;
|
|
39
32
|
};
|
|
40
33
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return input => {
|
|
46
|
-
const context = input;
|
|
47
|
-
const resources = context.resources ?? { clock: 1, recursions: [4] };
|
|
48
|
-
const { recursions } = resources;
|
|
49
|
-
assert(recursions.length > 0);
|
|
50
|
-
for (const recursion of rs) {
|
|
51
|
-
const rec = min(recursion, recursions.length - 1);
|
|
52
|
-
if (rec === -1) continue;
|
|
53
|
-
if (recursions[rec] < 1) throw new Error('Too much recursion');
|
|
54
|
-
--recursions[rec];
|
|
55
|
-
}
|
|
56
|
-
const result = parser(input);
|
|
57
|
-
for (const recursion of rs) {
|
|
58
|
-
const rec = min(recursion, recursions.length - 1);
|
|
59
|
-
if (rec === -1) continue;
|
|
60
|
-
++recursions[rec];
|
|
61
|
-
}
|
|
62
|
-
return result;
|
|
63
|
-
};
|
|
34
|
+
export function recur(recursions: number[], index: number, size: number, force: boolean = false): void {
|
|
35
|
+
index = min(index, recursions.length && recursions.length - 1);
|
|
36
|
+
if (recursions[index] < size - +force) throw new Error('Too much recursion');
|
|
37
|
+
recursions[index] -= size;
|
|
64
38
|
}
|
|
65
39
|
|
|
66
40
|
export function precedence<P extends Parser>(precedence: number, parser: P): P;
|
|
@@ -4,24 +4,31 @@ import { Delimiters } from '../delimiter';
|
|
|
4
4
|
type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number];
|
|
5
5
|
|
|
6
6
|
export function some<P extends Parser>(parser: P, limit?: number): P;
|
|
7
|
-
export function some<P extends Parser>(parser: P, delimiters?: readonly DelimiterOption[]): P;
|
|
8
|
-
export function some<P extends Parser>(parser: P, delimiter: string | RegExp, delimiters?: readonly DelimiterOption[]): P;
|
|
9
|
-
export function some<P extends Parser>(parser: P, delimiter: string | RegExp, after: string | RegExp, delimiters?: readonly DelimiterOption[]): P;
|
|
10
|
-
export function some<N>(parser: Parser<N>, delimiter?: number | string | RegExp | readonly DelimiterOption[], after?: string | RegExp | readonly DelimiterOption[], delimiters?: readonly DelimiterOption[], limit =
|
|
7
|
+
export function some<P extends Parser>(parser: P, delimiters?: readonly DelimiterOption[], limit?: number): P;
|
|
8
|
+
export function some<P extends Parser>(parser: P, delimiter: string | RegExp, delimiters?: readonly DelimiterOption[], limit?: number): P;
|
|
9
|
+
export function some<P extends Parser>(parser: P, delimiter: string | RegExp, after: string | RegExp, delimiters?: readonly DelimiterOption[], limit?: number): P;
|
|
10
|
+
export function some<N>(parser: Parser<N>, delimiter?: number | string | RegExp | readonly DelimiterOption[], after?: number | string | RegExp | readonly DelimiterOption[], delimiters?: number | readonly DelimiterOption[], limit = 0): Parser<N> {
|
|
11
11
|
if (typeof delimiter === 'number') {
|
|
12
12
|
limit = delimiter;
|
|
13
|
+
delimiters = undefined;
|
|
13
14
|
delimiter = undefined;
|
|
14
15
|
}
|
|
15
16
|
else if (Array.isArray(delimiter)) {
|
|
17
|
+
limit = after as number;
|
|
16
18
|
delimiters = delimiter;
|
|
17
19
|
delimiter = undefined;
|
|
18
20
|
}
|
|
19
21
|
else if (after === undefined || Array.isArray(after)) {
|
|
22
|
+
limit = delimiters as number;
|
|
20
23
|
delimiters = after;
|
|
21
24
|
after = undefined;
|
|
22
25
|
}
|
|
26
|
+
else {
|
|
27
|
+
delimiters = delimiters as readonly DelimiterOption[];
|
|
28
|
+
}
|
|
23
29
|
assert(parser);
|
|
24
30
|
assert(delimiter !== '');
|
|
31
|
+
assert(delimiters === undefined || Array.isArray(delimiters));
|
|
25
32
|
const match = Delimiters.tester(delimiter as string, after as string);
|
|
26
33
|
const delims = delimiters?.map(([delimiter, precedence]) => ({
|
|
27
34
|
signature: Delimiters.signature(delimiter),
|
|
@@ -31,7 +38,6 @@ export function some<N>(parser: Parser<N>, delimiter?: number | string | RegExp
|
|
|
31
38
|
return input => {
|
|
32
39
|
const context = input;
|
|
33
40
|
const { source, position } = context;
|
|
34
|
-
//assert(context.backtracks ??= {});
|
|
35
41
|
let nodes: List<Node<N>> | undefined;
|
|
36
42
|
delims && context.delimiters.push(delims);
|
|
37
43
|
// whileは数倍遅い
|
|
@@ -43,7 +49,8 @@ export function some<N>(parser: Parser<N>, delimiter?: number | string | RegExp
|
|
|
43
49
|
if (result === undefined) break;
|
|
44
50
|
if (context.position === pos) break;
|
|
45
51
|
nodes = nodes?.import(result) ?? result;
|
|
46
|
-
|
|
52
|
+
// 次にパースに成功すれば確実に制限値を超えるので制限値ちょうどでも中止する
|
|
53
|
+
if (limit > 0 && context.position - position >= limit) break;
|
|
47
54
|
}
|
|
48
55
|
delims && context.delimiters.pop(delims.length);
|
|
49
56
|
assert(context.position >= position);
|
package/src/parser/api/header.ts
CHANGED
|
@@ -8,7 +8,11 @@ export function header(source: string): string {
|
|
|
8
8
|
|
|
9
9
|
export function headers(source: string): string[] {
|
|
10
10
|
const [el] = parse(source);
|
|
11
|
-
|
|
11
|
+
const acc = [];
|
|
12
|
+
for (let field = el?.firstChild?.firstChild; field = field?.nextSibling;) {
|
|
13
|
+
acc.push(field.textContent!);
|
|
14
|
+
}
|
|
15
|
+
return acc;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
function parse(source: string): [HTMLElement, number] | [] {
|
|
@@ -125,7 +125,7 @@ describe('Unit: parser/api/parse', () => {
|
|
|
125
125
|
'!{../../a}',
|
|
126
126
|
].join('\n\n'), { host: new URL(`${location.origin}/z`) }).children].map(el => el.outerHTML),
|
|
127
127
|
[
|
|
128
|
-
'<aside class="header"><details open=""><summary>Header</summary><
|
|
128
|
+
'<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="https://source/x/y"><span class="field-name">URL</span>: <span class="field-value">https://source/x/y</span></div></details></aside>',
|
|
129
129
|
'<p><a class="account" href="https://source/@a" target="_blank">@a</a></p>',
|
|
130
130
|
'<p><a class="account" href="https://domain/@a" target="_blank">@domain/a</a></p>',
|
|
131
131
|
'<p><a class="channel" href="https://source/@a?ch=b" target="_blank">@a#b</a></p>',
|
|
@@ -158,7 +158,7 @@ describe('Unit: parser/api/parse', () => {
|
|
|
158
158
|
'{./a}',
|
|
159
159
|
].join('\n\n'), { host: new URL(`${location.origin}/index.md`) }).children].map(el => el.outerHTML),
|
|
160
160
|
[
|
|
161
|
-
'<aside class="header"><details open=""><summary>Header</summary><
|
|
161
|
+
'<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="https://source/x/y"><span class="field-name">URL</span>: <span class="field-value">https://source/x/y</span></div></details></aside>',
|
|
162
162
|
'<p><a class="url" href="/a">^/a</a></p>',
|
|
163
163
|
'<p><a class="url" href="https://source/x/a" target="_blank">./a</a></p>',
|
|
164
164
|
]);
|
|
@@ -173,7 +173,7 @@ describe('Unit: parser/api/parse', () => {
|
|
|
173
173
|
'{./a}',
|
|
174
174
|
].join('\n\n'), { host: new URL(`${location.origin}/z`) }).children].map(el => el.outerHTML),
|
|
175
175
|
[
|
|
176
|
-
`<aside class="header"><details open=""><summary>Header</summary><
|
|
176
|
+
`<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="${location.origin}/x/y"><span class="field-name">URL</span>: <span class="field-value">${location.origin}/x/y</span></div></details></aside>`,
|
|
177
177
|
'<p><a class="url" href="/z/a">^/a</a></p>',
|
|
178
178
|
'<p><a class="url" href="/x/a">./a</a></p>',
|
|
179
179
|
]);
|
|
@@ -204,9 +204,9 @@ describe('Unit: parser/api/parse', () => {
|
|
|
204
204
|
'{#}',
|
|
205
205
|
].join('\n\n'), { host: new URL(`${location.origin}/z`) }).children].map(el => normalize(el.outerHTML)),
|
|
206
206
|
[
|
|
207
|
-
`<aside class="header"><details open=""><summary>Header</summary><
|
|
207
|
+
`<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="https://example/x"><span class="field-name">URL</span>: <span class="field-value">https://example/x</span></div></details></aside>`,
|
|
208
208
|
'<pre class="invalid" translate="no">---\nURL: https://example/y\n---\n</pre>',
|
|
209
|
-
'<aside class="example" data-type="markdown"><pre translate="no">---\nURL: https://example/y\n---\n\n{#}</pre><hr><section><aside class="header"><details open=""><summary>Header</summary><
|
|
209
|
+
'<aside class="example" data-type="markdown"><pre translate="no">---\nURL: https://example/y\n---\n\n{#}</pre><hr><section><aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="https://example/y"><span class="field-name">URL</span>: <span class="field-value">https://example/y</span></div></details></aside><p><a class="url" href="https://example/y#" target="_blank">#</a></p><h2>References</h2><ol class="references"></ol></section></aside>',
|
|
210
210
|
'<p><a class="url" href="https://example/x#" target="_blank">#</a></p>',
|
|
211
211
|
]);
|
|
212
212
|
});
|
|
@@ -380,28 +380,28 @@ describe('Unit: parser/api/parse', () => {
|
|
|
380
380
|
// 最悪計算量での実行速度はCommonMarkの公式JS実装の32nに対して1-4倍程度。
|
|
381
381
|
// 5n = reference + link + url/math + ruby + text
|
|
382
382
|
assert.deepStrictEqual(
|
|
383
|
-
[...parse(`((([[[[#$[${'.'.repeat(
|
|
383
|
+
[...parse(`((([[[[#$http://[${'.'.repeat(19992)}`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
|
|
384
384
|
.map(el => el.tagName),
|
|
385
385
|
['P']);
|
|
386
386
|
});
|
|
387
387
|
|
|
388
388
|
it('backtrack 1 error', () => {
|
|
389
389
|
assert.deepStrictEqual(
|
|
390
|
-
[...parse(`((([[[[#$[${'.'.repeat(
|
|
390
|
+
[...parse(`((([[[[#$http://[${'.'.repeat(19992 + 1)}`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
|
|
391
391
|
.map(el => el.tagName),
|
|
392
392
|
['H1', 'PRE']);
|
|
393
393
|
});
|
|
394
394
|
|
|
395
395
|
it('backtrack 2', () => {
|
|
396
396
|
assert.deepStrictEqual(
|
|
397
|
-
[...parse(`((([[[[#$[${'.'.repeat(
|
|
397
|
+
[...parse(`((([[[[#$http://[${'.'.repeat(33324)}]]]`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
|
|
398
398
|
.map(el => el.tagName),
|
|
399
399
|
['P', 'OL']);
|
|
400
400
|
});
|
|
401
401
|
|
|
402
402
|
it('backtrack 2 error', () => {
|
|
403
403
|
assert.deepStrictEqual(
|
|
404
|
-
[...parse(`((([[[[#$[${'.'.repeat(
|
|
404
|
+
[...parse(`((([[[[#$http://[${'.'.repeat(33324 + 1)}]]]`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
|
|
405
405
|
.map(el => el.tagName),
|
|
406
406
|
['H1', 'PRE']);
|
|
407
407
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BlockquoteParser } from '../block';
|
|
2
2
|
import { Recursion } from '../context';
|
|
3
3
|
import { List, Node } from '../../combinator/data/parser';
|
|
4
|
-
import { union, some,
|
|
4
|
+
import { union, some, spend, recursion, block, validate, rewrite, open, convert, lazy, fmap } from '../../combinator';
|
|
5
5
|
import { autolink } from '../autolink';
|
|
6
6
|
import { contentline } from '../source';
|
|
7
7
|
import { unwrap, randomID } from '../util';
|
|
@@ -40,7 +40,7 @@ const markdown: BlockquoteParser.MarkdownParser = lazy(() => fmap(
|
|
|
40
40
|
rewrite(
|
|
41
41
|
some(contentline, opener),
|
|
42
42
|
convert(unindent, context => {
|
|
43
|
-
|
|
43
|
+
spend(context, 10);
|
|
44
44
|
const { source } = context;
|
|
45
45
|
const references = html('ol', { class: 'references' });
|
|
46
46
|
const document = parse(source, {
|
package/src/parser/block.ts
CHANGED
package/src/parser/context.ts
CHANGED
|
@@ -20,9 +20,9 @@ export class Context extends Ctx {
|
|
|
20
20
|
id,
|
|
21
21
|
caches,
|
|
22
22
|
} = options;
|
|
23
|
-
this.resources
|
|
23
|
+
this.resources = options.resources ?? {
|
|
24
24
|
// バックトラックのせいで文字数制限を受けないようにする。
|
|
25
|
-
clock: MAX_SEGMENT_SIZE * (
|
|
25
|
+
clock: MAX_SEGMENT_SIZE * (5 + 1),
|
|
26
26
|
recursions: [
|
|
27
27
|
5 || Recursion.block,
|
|
28
28
|
20 || Recursion.blockquote,
|
|
@@ -42,7 +42,7 @@ export class Context extends Ctx {
|
|
|
42
42
|
this.id = id;
|
|
43
43
|
this.caches = caches;
|
|
44
44
|
}
|
|
45
|
-
public override readonly resources
|
|
45
|
+
public override readonly resources: {
|
|
46
46
|
clock: number;
|
|
47
47
|
recursions: number[];
|
|
48
48
|
};
|
|
@@ -50,7 +50,7 @@ export class Context extends Ctx {
|
|
|
50
50
|
public local: boolean;
|
|
51
51
|
public sequential: boolean;
|
|
52
52
|
public buffer: List<Node<(string | HTMLElement)>>;
|
|
53
|
-
public recursion = new RecursionCounter(
|
|
53
|
+
public recursion = new RecursionCounter(2);
|
|
54
54
|
public readonly header: boolean;
|
|
55
55
|
public readonly host?: URL;
|
|
56
56
|
public readonly url?: URL;
|
|
@@ -65,7 +65,6 @@ export type Options = Partial<Context>;
|
|
|
65
65
|
|
|
66
66
|
class RecursionCounter {
|
|
67
67
|
constructor(
|
|
68
|
-
private readonly syntax: string,
|
|
69
68
|
private readonly limit: number,
|
|
70
69
|
) {
|
|
71
70
|
}
|
|
@@ -75,7 +74,7 @@ class RecursionCounter {
|
|
|
75
74
|
const { stack } = this
|
|
76
75
|
for (; this.index > 0 && stack[this.index - 1] <= depth; --this.index);
|
|
77
76
|
// 内側から数えるので無効化処理できずエラーを投げるしかない。
|
|
78
|
-
if (this.index === this.limit) throw new Error(`Too much
|
|
77
|
+
if (this.index === this.limit) throw new Error(`Too much recursion`);
|
|
79
78
|
stack[this.index] = depth;
|
|
80
79
|
++this.index;
|
|
81
80
|
}
|
|
@@ -24,11 +24,11 @@ describe('Unit: parser/header', () => {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('basic', () => {
|
|
27
|
-
assert.deepStrictEqual(inspect(parser, input('---\na: b\n---', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><
|
|
28
|
-
assert.deepStrictEqual(inspect(parser, input('---\na: b\n---\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><
|
|
29
|
-
assert.deepStrictEqual(inspect(parser, input('---\na: b\nC: D e\n---\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><
|
|
30
|
-
assert.deepStrictEqual(inspect(parser, input('---\r\na: b\r\nC: D e\r\n---\r\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><
|
|
31
|
-
assert.deepStrictEqual(inspect(parser, input('----\na: b\n----', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><
|
|
27
|
+
assert.deepStrictEqual(inspect(parser, input('---\na: b\n---', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div></details></aside>'], '']);
|
|
28
|
+
assert.deepStrictEqual(inspect(parser, input('---\na: b\n---\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div></details></aside>'], '']);
|
|
29
|
+
assert.deepStrictEqual(inspect(parser, input('---\na: b\nC: D e\n---\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div><div class="field" data-name="c" data-value="D e"><span class="field-name">C</span>: <span class="field-value">D e</span></div></details></aside>'], '']);
|
|
30
|
+
assert.deepStrictEqual(inspect(parser, input('---\r\na: b\r\nC: D e\r\n---\r\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div><div class="field" data-name="c" data-value="D e"><span class="field-name">C</span>: <span class="field-value">D e</span></div></details></aside>'], '']);
|
|
31
|
+
assert.deepStrictEqual(inspect(parser, input('----\na: b\n----', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div></details></aside>'], '']);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
});
|
package/src/parser/header.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const header: MarkdownParser.HeaderParser = lazy(() => validate(
|
|
|
11
11
|
block(
|
|
12
12
|
union([
|
|
13
13
|
validate(context => context.header,
|
|
14
|
-
focus(/(---+)[^\S\r\n]*\r?\n(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*:[ \t]+\S[^\r\n]*\r?\n){1,
|
|
14
|
+
focus(/(---+)[^\S\r\n]*\r?\n(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*:[ \t]+\S[^\r\n]*\r?\n){1,32}\1[^\S\r\n]*(?:$|\r?\n)/yi,
|
|
15
15
|
convert(source =>
|
|
16
16
|
source.slice(source.indexOf('\n') + 1, source.trimEnd().lastIndexOf('\n')),
|
|
17
17
|
fmap(
|
|
@@ -42,11 +42,10 @@ const field: MarkdownParser.HeaderParser.FieldParser = line(({ source, position
|
|
|
42
42
|
const name = source.slice(position, source.indexOf(':', position));
|
|
43
43
|
const value = source.slice(position + name.length + 1).trim();
|
|
44
44
|
return new List([
|
|
45
|
-
new Node(html('
|
|
45
|
+
new Node(html('div', { class: 'field', 'data-name': name.toLowerCase(), 'data-value': value }, [
|
|
46
46
|
html('span', { class: 'field-name' }, name),
|
|
47
47
|
': ',
|
|
48
48
|
html('span', { class: 'field-value' }, value),
|
|
49
|
-
'\n',
|
|
50
49
|
])),
|
|
51
50
|
]);
|
|
52
51
|
});
|
|
@@ -25,7 +25,7 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
25
25
|
// 常に非常に非効率な処理を行い常時低速化するより三重以上の注釈を禁止して効率性を維持するのが妥当である。
|
|
26
26
|
const MAX_DEPTH = 20;
|
|
27
27
|
export const annotation: AnnotationParser = lazy(() => constraint(State.annotation,
|
|
28
|
-
repeat('(', beforeNonblank, ')',
|
|
28
|
+
repeat('(', beforeNonblank, ')', Recursion.bracket, precedence(1, surround(
|
|
29
29
|
'',
|
|
30
30
|
some(union([inline]), ')', [[')', 1]]),
|
|
31
31
|
')',
|
|
@@ -41,17 +41,22 @@ export const annotation: AnnotationParser = lazy(() => constraint(State.annotati
|
|
|
41
41
|
new Node(html('span', { class: bracketname(context, 1, 1) }, defrag(unwrap(nodes))))
|
|
42
42
|
]);
|
|
43
43
|
}
|
|
44
|
-
recursion.add(
|
|
44
|
+
recursion.add(
|
|
45
|
+
MAX_DEPTH - (resources?.recursions[Recursion.bracket] ?? resources?.recursions.at(-1) ?? MAX_DEPTH));
|
|
45
46
|
context.position += 1;
|
|
46
47
|
return new List([
|
|
47
|
-
new Node(html('sup', { class: 'annotation' }, [
|
|
48
|
+
new Node(html('sup', { class: 'annotation' }, [
|
|
49
|
+
html('span', defrag(unwrap(trimBlankNodeEnd(nodes))))
|
|
50
|
+
]))
|
|
48
51
|
]);
|
|
49
52
|
},
|
|
50
53
|
(nodes, context, prefix, postfix) => {
|
|
51
54
|
assert(postfix === 0);
|
|
52
55
|
for (let i = 0; i < prefix; ++i) {
|
|
53
56
|
nodes.unshift(new Node('('));
|
|
54
|
-
nodes = new List([
|
|
57
|
+
nodes = new List([
|
|
58
|
+
new Node(html('span', { class: bracketname(context, 0, 0) }, defrag(unwrap(nodes))))
|
|
59
|
+
]);
|
|
55
60
|
context.range += 1;
|
|
56
61
|
}
|
|
57
62
|
return nodes;
|
|
@@ -2,13 +2,12 @@ import { AutolinkParser } from '../../inline';
|
|
|
2
2
|
import { State, Recursion, Backtrack } from '../../context';
|
|
3
3
|
import { List, Node } from '../../../combinator/data/parser';
|
|
4
4
|
import { union, tails, some, recursion, precedence, state, constraint, focus, rewrite, surround, open, lazy } from '../../../combinator';
|
|
5
|
-
import { inline } from '../../inline';
|
|
6
5
|
import { parse } from '../link';
|
|
7
6
|
import { unescsource, str } from '../../source';
|
|
8
7
|
|
|
9
8
|
export const url: AutolinkParser.UrlParser = lazy(() => rewrite(
|
|
10
9
|
open(
|
|
11
|
-
/(?<![0-9A-Za-z][.+-]?|[@#])https?:\/\/(?=[
|
|
10
|
+
/(?<![0-9A-Za-z][.+-]?|[@#])https?:\/\/(?=[[0-9A-Za-z])/y,
|
|
12
11
|
precedence(0, some(union([
|
|
13
12
|
some(unescsource, /(?<![-+*=~^_,.;:!?]|\/{3})(?:[-+*=~^_,.;:!?]|\/{3,}(?!\/))*(?=[\\$"`\[\](){}<>()[]{}|]|[^\x21-\x7E]|$)/y),
|
|
14
13
|
precedence(1, bracket),
|
|
@@ -18,7 +17,7 @@ export const url: AutolinkParser.UrlParser = lazy(() => rewrite(
|
|
|
18
17
|
union([
|
|
19
18
|
constraint(State.autolink, state(State.autolink, context =>
|
|
20
19
|
new List([new Node(parse(new List(), new List([new Node(context.source)]), context))]))),
|
|
21
|
-
|
|
20
|
+
context => new List([new Node(context.source)]),
|
|
22
21
|
])));
|
|
23
22
|
|
|
24
23
|
export const lineurl: AutolinkParser.UrlParser.LineUrlParser = lazy(() => focus(
|
|
@@ -36,7 +35,7 @@ export const lineurl: AutolinkParser.UrlParser.LineUrlParser = lazy(() => focus(
|
|
|
36
35
|
context))
|
|
37
36
|
]);
|
|
38
37
|
})),
|
|
39
|
-
|
|
38
|
+
context => new List([new Node(context.source)]),
|
|
40
39
|
]),
|
|
41
40
|
])));
|
|
42
41
|
|
|
@@ -9,7 +9,7 @@ import { unwrap } from '../util';
|
|
|
9
9
|
import { html, defrag } from 'typed-dom/dom';
|
|
10
10
|
|
|
11
11
|
export const deletion: DeletionParser = lazy(() =>
|
|
12
|
-
repeat('~~', '', '~~',
|
|
12
|
+
repeat('~~', '', '~~', Recursion.inline, precedence(0, surround(
|
|
13
13
|
'',
|
|
14
14
|
some(union([
|
|
15
15
|
some(inline, blankWith('\n', '~~')),
|
|
@@ -24,7 +24,7 @@ const subemphasis: Parser.IntermediateParser<EmphasisParser> = lazy(() => some(u
|
|
|
24
24
|
// 可能な限り早く閉じるよう解析しなければならない。
|
|
25
25
|
// このため終端記号の後ろを見て終端を中止し同じ構文を再帰的に適用してはならない。
|
|
26
26
|
export const emstrong: EmStrongParser = lazy(() =>
|
|
27
|
-
repeat('***', beforeNonblank, '***',
|
|
27
|
+
repeat('***', beforeNonblank, '***', Recursion.inline, precedence(0, surround(
|
|
28
28
|
'',
|
|
29
29
|
some(union([some(inline, '*', afterNonblank)])),
|
|
30
30
|
strs('*', 1, 3),
|
|
@@ -9,7 +9,7 @@ import { unwrap } from '../util';
|
|
|
9
9
|
import { html, defrag } from 'typed-dom/dom';
|
|
10
10
|
|
|
11
11
|
export const insertion: InsertionParser = lazy(() =>
|
|
12
|
-
repeat('++', '', '++',
|
|
12
|
+
repeat('++', '', '++', Recursion.inline, precedence(0, surround(
|
|
13
13
|
'',
|
|
14
14
|
some(union([
|
|
15
15
|
some(inline, blankWith('\n', '++')),
|
|
@@ -12,7 +12,7 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
12
12
|
// 斜体は単語に使うとかえって見づらく読み飛ばしやすくなるため使わないべきであり
|
|
13
13
|
// ある程度の長さのある文に使うのが望ましい。
|
|
14
14
|
export const italic: ItalicParser = lazy(() =>
|
|
15
|
-
repeat('///', beforeNonblank, '///',
|
|
15
|
+
repeat('///', beforeNonblank, '///', Recursion.inline, precedence(0, surround(
|
|
16
16
|
'',
|
|
17
17
|
some(union([inline]), '///', afterNonblank),
|
|
18
18
|
'///',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LinkParser } from '../inline';
|
|
2
2
|
import { Context, State, Backtrack, Command } from '../context';
|
|
3
3
|
import { List, Node } from '../../combinator/data/parser';
|
|
4
|
-
import { union, inits, sequence, subsequence, some,
|
|
4
|
+
import { union, inits, sequence, subsequence, some, spend, precedence, state, constraint, surround, open, setBacktrack, dup, lazy, fmap, bind } from '../../combinator';
|
|
5
5
|
import { inline, media, shortmedia } from '../inline';
|
|
6
6
|
import { attributes } from './html';
|
|
7
7
|
import { str } from '../source';
|
|
@@ -109,7 +109,7 @@ export function parse(
|
|
|
109
109
|
const INSECURE_URI = params.shift()!.value;
|
|
110
110
|
assert(INSECURE_URI === INSECURE_URI.trim());
|
|
111
111
|
assert(!INSECURE_URI.match(/\s/));
|
|
112
|
-
|
|
112
|
+
spend(context, 10);
|
|
113
113
|
let uri: ReadonlyURL | undefined;
|
|
114
114
|
try{
|
|
115
115
|
uri = new ReadonlyURL(
|
|
@@ -10,7 +10,7 @@ import { unwrap } from '../util';
|
|
|
10
10
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
11
11
|
|
|
12
12
|
export const mark: MarkParser = lazy(() =>
|
|
13
|
-
repeat('==', beforeNonblank, '==',
|
|
13
|
+
repeat('==', beforeNonblank, '==', Recursion.inline, precedence(0, surround(
|
|
14
14
|
'',
|
|
15
15
|
state(State.mark, some(union([inline]), '==', afterNonblank)),
|
|
16
16
|
'==',
|
|
@@ -93,8 +93,8 @@ describe('Unit: parser/inline/math', () => {
|
|
|
93
93
|
assert.deepStrictEqual(inspect(parser, input('$\\Begin$', new Context())), [['<span class="invalid" translate="no">$\\Begin$</span>'], '']);
|
|
94
94
|
assert.deepStrictEqual(inspect(parser, input('$\\begin{}$', new Context())), [['<span class="invalid" translate="no">$\\begin{}$</span>'], '']);
|
|
95
95
|
assert.deepStrictEqual(inspect(parser, input('${\\begin}$', new Context())), [['<span class="invalid" translate="no">${\\begin}$</span>'], '']);
|
|
96
|
-
assert.deepStrictEqual(inspect(parser, input('$http://host$', new Context())),
|
|
97
|
-
assert.deepStrictEqual(inspect(parser, input('${http://host}$', new Context())),
|
|
96
|
+
assert.deepStrictEqual(inspect(parser, input('$http://host$', new Context())), undefined);
|
|
97
|
+
assert.deepStrictEqual(inspect(parser, input('${http://host}$', new Context())), undefined);
|
|
98
98
|
assert.deepStrictEqual(inspect(parser, input(' ${a}$', new Context())), undefined);
|
|
99
99
|
});
|
|
100
100
|
|
|
@@ -6,7 +6,7 @@ import { escsource, str } from '../source';
|
|
|
6
6
|
import { invalid } from '../util';
|
|
7
7
|
import { html } from 'typed-dom/dom';
|
|
8
8
|
|
|
9
|
-
const forbiddenCommand = /\\(?:begin|tiny|huge|large)(?![a-z])
|
|
9
|
+
const forbiddenCommand = /\\(?:begin|tiny|huge|large)(?![a-z])/i;
|
|
10
10
|
|
|
11
11
|
export const math: MathParser = lazy(() => rewrite(
|
|
12
12
|
union([
|
|
@@ -19,7 +19,7 @@ export const math: MathParser = lazy(() => rewrite(
|
|
|
19
19
|
surround(
|
|
20
20
|
/\$(?![\s{}])/y,
|
|
21
21
|
precedence(2, some(union([
|
|
22
|
-
some(escsource,
|
|
22
|
+
some(escsource, /[`"{}$\r\n]|(?<=[0-9A-Za-z]):\/\/[[0-9A-Za-z]/y),
|
|
23
23
|
precedence(4, bracket),
|
|
24
24
|
]))),
|
|
25
25
|
/(?<!\s)\$(?![-0-9A-Za-z])/y,
|
|
@@ -45,7 +45,7 @@ const bracket: MathParser.BracketParser = lazy(() => surround(
|
|
|
45
45
|
recursion(Recursion.terminal,
|
|
46
46
|
some(union([
|
|
47
47
|
bracket,
|
|
48
|
-
some(escsource, /[{}$\r\n]/y),
|
|
48
|
+
some(escsource, /[{}$\r\n]|(?<=[0-9A-Za-z]):\/\/[[0-9A-Za-z]/y),
|
|
49
49
|
]))),
|
|
50
50
|
str('}'),
|
|
51
51
|
true));
|